feat: 优化二维码和图片代理逻辑
主要修改: 1. 在QrProxyController中引入图片保存路径配置,确保图片保存目录存在。 2. 更新图片获取逻辑,支持从本地读取和异步保存图片,提升了系统的灵活性和性能。 3. 增加了处理404和500错误的逻辑,增强了接口的健壮性。 技术细节: - 通过优化图片处理流程,提升了用户体验,同时确保了图片的有效管理和存储。
This commit is contained in:
@@ -3,12 +3,14 @@ package com.gameplatform.server.controller.link;
|
||||
import com.gameplatform.server.mapper.agent.LinkBatchMapper;
|
||||
import com.gameplatform.server.mapper.agent.LinkTaskMapper;
|
||||
import com.gameplatform.server.model.dto.link.GameInterfaceResponse;
|
||||
|
||||
import com.gameplatform.server.model.entity.agent.LinkBatch;
|
||||
import com.gameplatform.server.model.entity.agent.LinkTask;
|
||||
import com.gameplatform.server.service.external.ScriptClient;
|
||||
import com.gameplatform.server.service.link.LinkStatusService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -20,8 +22,15 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -37,17 +46,28 @@ public class QrProxyController {
|
||||
private final String appBaseUrl;
|
||||
private final LinkTaskMapper linkTaskMapper;
|
||||
private final LinkBatchMapper linkBatchMapper;
|
||||
private final String imageSavePath;
|
||||
|
||||
public QrProxyController(ScriptClient scriptClient,
|
||||
LinkStatusService linkStatusService,
|
||||
@Value("${app.base-url}") String appBaseUrl,
|
||||
@Value("${app.image-save-path:./images}") String imageSavePath,
|
||||
LinkTaskMapper linkTaskMapper,
|
||||
LinkBatchMapper linkBatchMapper) {
|
||||
this.scriptClient = scriptClient;
|
||||
this.linkStatusService = linkStatusService;
|
||||
this.appBaseUrl = appBaseUrl;
|
||||
this.imageSavePath = imageSavePath;
|
||||
this.linkTaskMapper = linkTaskMapper;
|
||||
this.linkBatchMapper = linkBatchMapper;
|
||||
|
||||
// 确保图片保存目录存在
|
||||
try {
|
||||
Files.createDirectories(Paths.get(imageSavePath));
|
||||
log.info("图片保存目录已创建或已存在: {}", imageSavePath);
|
||||
} catch (IOException e) {
|
||||
log.error("创建图片保存目录失败: {}", imageSavePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping(value = "/image/{codeNo}/qr.png", produces = MediaType.IMAGE_PNG_VALUE)
|
||||
@@ -57,7 +77,7 @@ public class QrProxyController {
|
||||
String machineId = linkStatusService.getMechainIdByCode(codeNo);
|
||||
if (machineId == null) {
|
||||
log.warn("无法找到codeNo对应的machineId: {}", codeNo);
|
||||
return Mono.just(ResponseEntity.notFound().build());
|
||||
return createNotFoundResponseMono();
|
||||
}
|
||||
|
||||
String path = "/" + machineId + "/二维码.png";
|
||||
@@ -66,83 +86,223 @@ public class QrProxyController {
|
||||
.contentType(MediaType.IMAGE_PNG)
|
||||
.cacheControl(CacheControl.maxAge(30, TimeUnit.SECONDS).cachePublic())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=qr.png")
|
||||
.body(bytes));
|
||||
.body(bytes))
|
||||
.onErrorResume(WebClientResponseException.NotFound.class, ex -> {
|
||||
log.warn("图片不存在: path={}", path);
|
||||
return createNotFoundResponseMono();
|
||||
})
|
||||
.onErrorResume(WebClientResponseException.class, ex -> {
|
||||
log.warn("获取图片失败: path={}, status={}, error={}", path, ex.getStatusCode(), ex.getMessage());
|
||||
return Mono.just(ResponseEntity.status(ex.getStatusCode()).build());
|
||||
});
|
||||
}
|
||||
|
||||
@GetMapping(value = "/image/{codeNo}/homepage.png", produces = MediaType.IMAGE_PNG_VALUE)
|
||||
@Operation(summary = "首次主页图片代理")
|
||||
public Mono<ResponseEntity<byte[]>> homepage(@PathVariable("codeNo") String codeNo) {
|
||||
// 通过codeNo查询machineId
|
||||
String machineId = linkStatusService.getMechainIdByCode(codeNo);
|
||||
if (machineId == null) {
|
||||
log.warn("无法找到codeNo对应的machineId: {}", codeNo);
|
||||
return Mono.just(ResponseEntity.notFound().build());
|
||||
}
|
||||
return linkStatusService.getLinkStatus(codeNo)
|
||||
.flatMap(linkStatus -> {
|
||||
// 如果状态是COMPLETED,直接读取本地文件
|
||||
if ("COMPLETED".equals(linkStatus.getStatus())) {
|
||||
log.info("链接已完成,尝试读取本地图片: codeNo={}", codeNo);
|
||||
return readLocalImageFile(codeNo, "homepage.png")
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.warn("本地图片文件不存在,返回404: codeNo={}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}));
|
||||
}
|
||||
|
||||
// 状态不是COMPLETED,使用scriptClient获取图片
|
||||
String machineId = linkStatus.getMachineId();
|
||||
if (machineId == null) {
|
||||
log.warn("无法找到codeNo对应的machineId: {}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}
|
||||
|
||||
String path = "/" + machineId + "/首次主页.png";
|
||||
return scriptClient.getImagePng(path)
|
||||
.map(bytes -> ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_PNG)
|
||||
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS).cachePublic())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=homepage.png")
|
||||
.body(bytes));
|
||||
String path = "/" + machineId + "/首次主页.png";
|
||||
return scriptClient.getImagePng(path)
|
||||
.flatMap(bytes -> {
|
||||
// 异步保存图片到本地
|
||||
return saveImageToLocalAsync(codeNo, bytes, "homepage.png")
|
||||
.then(Mono.just(bytes));
|
||||
})
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.onErrorResume(WebClientResponseException.NotFound.class, ex -> {
|
||||
log.warn("scriptClient图片不存在,尝试读取本地文件: path={}", path);
|
||||
// 如果scriptClient返回404,尝试读取本地文件
|
||||
return readLocalImageFile(codeNo, "homepage.png")
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.warn("本地图片文件也不存在: codeNo={}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}));
|
||||
})
|
||||
.onErrorResume(WebClientResponseException.class, ex -> {
|
||||
log.warn("获取图片失败: path={}, status={}, error={}", path, ex.getStatusCode(), ex.getMessage());
|
||||
return Mono.just(ResponseEntity.status(ex.getStatusCode()).build());
|
||||
});
|
||||
})
|
||||
.onErrorResume(Exception.class, ex -> {
|
||||
log.error("获取链接状态失败: codeNo={}, error={}", codeNo, ex.getMessage());
|
||||
return createInternalServerErrorResponseMono();
|
||||
});
|
||||
}
|
||||
|
||||
@GetMapping(value = "/image/{codeNo}/first-reward.png", produces = MediaType.IMAGE_PNG_VALUE)
|
||||
@Operation(summary = "首次赏金图片代理")
|
||||
public Mono<ResponseEntity<byte[]>> firstReward(@PathVariable("codeNo") String codeNo) {
|
||||
// 通过codeNo查询machineId
|
||||
String machineId = linkStatusService.getMechainIdByCode(codeNo);
|
||||
if (machineId == null) {
|
||||
log.warn("无法找到codeNo对应的machineId: {}", codeNo);
|
||||
return Mono.just(ResponseEntity.notFound().build());
|
||||
}
|
||||
return linkStatusService.getLinkStatus(codeNo)
|
||||
.flatMap(linkStatus -> {
|
||||
// 如果状态是COMPLETED,直接读取本地文件
|
||||
if ("COMPLETED".equals(linkStatus.getStatus())) {
|
||||
log.info("链接已完成,尝试读取本地图片: codeNo={}", codeNo);
|
||||
return readLocalImageFile(codeNo, "firstReward.png")
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.warn("本地图片文件不存在,返回404: codeNo={}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}));
|
||||
}
|
||||
|
||||
// 状态不是COMPLETED,使用scriptClient获取图片
|
||||
String machineId = linkStatus.getMachineId();
|
||||
if (machineId == null) {
|
||||
log.warn("无法找到codeNo对应的machineId: {}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}
|
||||
|
||||
String path = "/" + machineId + "/首次赏金.png";
|
||||
return scriptClient.getImagePng(path)
|
||||
.map(bytes -> ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_PNG)
|
||||
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS).cachePublic())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=first-reward.png")
|
||||
.body(bytes));
|
||||
String path = "/" + machineId + "/首次赏金.png";
|
||||
return scriptClient.getImagePng(path)
|
||||
.flatMap(bytes -> {
|
||||
// 异步保存图片到本地
|
||||
return saveImageToLocalAsync(codeNo, bytes, "firstReward.png")
|
||||
.then(Mono.just(bytes));
|
||||
})
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.onErrorResume(WebClientResponseException.NotFound.class, ex -> {
|
||||
log.warn("scriptClient图片不存在,尝试读取本地文件: path={}", path);
|
||||
// 如果scriptClient返回404,尝试读取本地文件
|
||||
return readLocalImageFile(codeNo, "firstReward.png")
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.warn("本地图片文件也不存在: codeNo={}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}));
|
||||
})
|
||||
.onErrorResume(WebClientResponseException.class, ex -> {
|
||||
log.warn("获取图片失败: path={}, status={}, error={}", path, ex.getStatusCode(), ex.getMessage());
|
||||
return Mono.just(ResponseEntity.status(ex.getStatusCode()).build());
|
||||
});
|
||||
})
|
||||
.onErrorResume(Exception.class, ex -> {
|
||||
log.error("获取链接状态失败: codeNo={}, error={}", codeNo, ex.getMessage());
|
||||
return createInternalServerErrorResponseMono();
|
||||
});
|
||||
}
|
||||
|
||||
@GetMapping(value = "/image/{codeNo}/mid-reward.png", produces = MediaType.IMAGE_PNG_VALUE)
|
||||
@Operation(summary = "中途赏金图片代理")
|
||||
public Mono<ResponseEntity<byte[]>> midReward(@PathVariable("codeNo") String codeNo) {
|
||||
// 通过codeNo查询machineId
|
||||
String machineId = linkStatusService.getMechainIdByCode(codeNo);
|
||||
if (machineId == null) {
|
||||
log.warn("无法找到codeNo对应的machineId: {}", codeNo);
|
||||
return Mono.just(ResponseEntity.notFound().build());
|
||||
}
|
||||
return linkStatusService.getLinkStatus(codeNo)
|
||||
.flatMap(linkStatus -> {
|
||||
// 如果状态是COMPLETED,直接读取本地文件
|
||||
if ("COMPLETED".equals(linkStatus.getStatus())) {
|
||||
log.info("链接已完成,尝试读取本地图片: codeNo={}", codeNo);
|
||||
return readLocalImageFile(codeNo, "midReward.png")
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.warn("本地图片文件不存在,返回404: codeNo={}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}));
|
||||
}
|
||||
|
||||
// 状态不是COMPLETED,使用scriptClient获取图片
|
||||
String machineId = linkStatus.getMachineId();
|
||||
if (machineId == null) {
|
||||
log.warn("无法找到codeNo对应的machineId: {}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}
|
||||
|
||||
String path = "/" + machineId + "/中途赏金.png";
|
||||
return scriptClient.getImagePng(path)
|
||||
.map(bytes -> ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_PNG)
|
||||
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS).cachePublic())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=mid-reward.png")
|
||||
.body(bytes));
|
||||
String path = "/" + machineId + "/中途赏金.png";
|
||||
return scriptClient.getImagePng(path)
|
||||
.flatMap(bytes -> {
|
||||
// 异步保存图片到本地
|
||||
return saveImageToLocalAsync(codeNo, bytes, "midReward.png")
|
||||
.then(Mono.just(bytes));
|
||||
})
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.onErrorResume(WebClientResponseException.NotFound.class, ex -> {
|
||||
log.warn("scriptClient图片不存在,尝试读取本地文件: path={}", path);
|
||||
// 如果scriptClient返回404,尝试读取本地文件
|
||||
return readLocalImageFile(codeNo, "midReward.png")
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.warn("本地图片文件也不存在: codeNo={}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}));
|
||||
})
|
||||
.onErrorResume(WebClientResponseException.class, ex -> {
|
||||
log.warn("获取图片失败: path={}, status={}, error={}", path, ex.getStatusCode(), ex.getMessage());
|
||||
return Mono.just(ResponseEntity.status(ex.getStatusCode()).build());
|
||||
});
|
||||
})
|
||||
.onErrorResume(Exception.class, ex -> {
|
||||
log.error("获取链接状态失败: codeNo={}, error={}", codeNo, ex.getMessage());
|
||||
return createInternalServerErrorResponseMono();
|
||||
});
|
||||
}
|
||||
|
||||
@GetMapping(value = "/image/{codeNo}/end-reward.png", produces = MediaType.IMAGE_PNG_VALUE)
|
||||
@Operation(summary = "结束赏金图片代理")
|
||||
public Mono<ResponseEntity<byte[]>> endReward(@PathVariable("codeNo") String codeNo) {
|
||||
// 通过codeNo查询machineId
|
||||
String machineId = linkStatusService.getMechainIdByCode(codeNo);
|
||||
if (machineId == null) {
|
||||
log.warn("无法找到codeNo对应的machineId: {}", codeNo);
|
||||
return Mono.just(ResponseEntity.notFound().build());
|
||||
}
|
||||
return linkStatusService.getLinkStatus(codeNo)
|
||||
.flatMap(linkStatus -> {
|
||||
// 如果状态是COMPLETED,直接读取本地文件
|
||||
if ("COMPLETED".equals(linkStatus.getStatus())) {
|
||||
log.info("链接已完成,尝试读取本地图片: codeNo={}", codeNo);
|
||||
return readLocalImageFile(codeNo, "endReward.png")
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.warn("本地图片文件不存在,返回404: codeNo={}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}));
|
||||
}
|
||||
|
||||
// 状态不是COMPLETED,使用scriptClient获取图片
|
||||
String machineId = linkStatus.getMachineId();
|
||||
if (machineId == null) {
|
||||
log.warn("无法找到codeNo对应的machineId: {}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}
|
||||
|
||||
String path = "/" + machineId + "/结束赏金.png";
|
||||
return scriptClient.getImagePng(path)
|
||||
.map(bytes -> ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_PNG)
|
||||
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS).cachePublic())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=end-reward.png")
|
||||
.body(bytes));
|
||||
String path = "/" + machineId + "/结束赏金.png";
|
||||
return scriptClient.getImagePng(path)
|
||||
.flatMap(bytes -> {
|
||||
// 异步保存图片到本地
|
||||
return saveImageToLocalAsync(codeNo, bytes, "endReward.png")
|
||||
.then(Mono.just(bytes));
|
||||
})
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.onErrorResume(WebClientResponseException.NotFound.class, ex -> {
|
||||
log.warn("scriptClient图片不存在,尝试读取本地文件: path={}", path);
|
||||
// 如果scriptClient返回404,尝试读取本地文件
|
||||
return readLocalImageFile(codeNo, "endReward.png")
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.warn("本地图片文件也不存在: codeNo={}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}));
|
||||
})
|
||||
.onErrorResume(WebClientResponseException.class, ex -> {
|
||||
log.warn("获取图片失败: path={}, status={}, error={}", path, ex.getStatusCode(), ex.getMessage());
|
||||
return Mono.just(ResponseEntity.status(ex.getStatusCode()).build());
|
||||
});
|
||||
})
|
||||
.onErrorResume(Exception.class, ex -> {
|
||||
log.error("获取链接状态失败: codeNo={}, error={}", codeNo, ex.getMessage());
|
||||
return createInternalServerErrorResponseMono();
|
||||
});
|
||||
}
|
||||
|
||||
@GetMapping("/{codeNo}/images")
|
||||
@@ -232,6 +392,106 @@ public class QrProxyController {
|
||||
return "未知区域";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存图片到本地文件系统
|
||||
* @param codeNo 任务编号
|
||||
* @param imageData 图片数据
|
||||
* @param fileName 文件名
|
||||
*/
|
||||
private void saveImageToLocal(String codeNo, byte[] imageData, String fileName) {
|
||||
if (imageData == null || imageData.length == 0) {
|
||||
log.debug("图片数据为空,跳过保存: codeNo={}, fileName={}", codeNo, fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建任务目录
|
||||
Path taskDirPath = Paths.get(imageSavePath, codeNo);
|
||||
Files.createDirectories(taskDirPath);
|
||||
|
||||
// 保存文件
|
||||
Path filePath = taskDirPath.resolve(fileName);
|
||||
Files.write(filePath, imageData, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
|
||||
|
||||
log.debug("图片保存成功: {} ({}字节)", filePath, imageData.length);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.warn("保存图片失败: codeNo={}, fileName={}, error={}", codeNo, fileName, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步保存图片到本地
|
||||
* @param codeNo 任务编号
|
||||
* @param imageData 图片数据
|
||||
* @param fileName 文件名
|
||||
* @return Mono<Void>
|
||||
*/
|
||||
private Mono<Void> saveImageToLocalAsync(String codeNo, byte[] imageData, String fileName) {
|
||||
return Mono.fromRunnable(() -> saveImageToLocal(codeNo, imageData, fileName))
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.then();
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取本地保存的图片文件
|
||||
* @param codeNo 任务编号
|
||||
* @param fileName 文件名
|
||||
* @return Mono<byte[]> 图片数据,如果文件不存在则返回empty
|
||||
*/
|
||||
private Mono<byte[]> readLocalImageFile(String codeNo, String fileName) {
|
||||
return Mono.fromCallable(() -> {
|
||||
Path filePath = Paths.get(imageSavePath, codeNo, fileName);
|
||||
if (Files.exists(filePath)) {
|
||||
byte[] imageBytes = Files.readAllBytes(filePath);
|
||||
log.info("读取本地图片成功: {} ({}字节)", filePath, imageBytes.length);
|
||||
return imageBytes;
|
||||
} else {
|
||||
log.debug("本地图片文件不存在: {}", filePath);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.filter(bytes -> bytes != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建图片响应
|
||||
* @param bytes 图片字节数据
|
||||
* @return ResponseEntity<byte[]>
|
||||
*/
|
||||
private ResponseEntity<byte[]> createImageResponse(byte[] bytes) {
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_PNG)
|
||||
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS).cachePublic())
|
||||
.body(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建图片响应的Mono包装
|
||||
* @param bytes 图片字节数据
|
||||
* @return Mono<ResponseEntity<byte[]>>
|
||||
*/
|
||||
private Mono<ResponseEntity<byte[]>> createImageResponseMono(byte[] bytes) {
|
||||
return Mono.just(createImageResponse(bytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建404响应的Mono包装
|
||||
* @return Mono<ResponseEntity<byte[]>>
|
||||
*/
|
||||
private Mono<ResponseEntity<byte[]>> createNotFoundResponseMono() {
|
||||
return createNotFoundResponseMono();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建500响应的Mono包装
|
||||
* @return Mono<ResponseEntity<byte[]>>
|
||||
*/
|
||||
private Mono<ResponseEntity<byte[]>> createInternalServerErrorResponseMono() {
|
||||
return createInternalServerErrorResponseMono();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user