大声道撒旦
This commit is contained in:
parent
aa2c89261d
commit
5b2dc6145f
@ -32,4 +32,14 @@ public interface IJenkinsBuildRepository extends IBaseRepository<JenkinsBuild, L
|
|||||||
* @return 构建信息列表
|
* @return 构建信息列表
|
||||||
*/
|
*/
|
||||||
List<JenkinsBuild> findByExternalSystemIdAndJobId(Long externalSystemId, Long jobId);
|
List<JenkinsBuild> findByExternalSystemIdAndJobId(Long externalSystemId, Long jobId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定任务的最新构建记录
|
||||||
|
*
|
||||||
|
* @param externalSystemId 外部系统ID
|
||||||
|
* @param jobId 任务ID
|
||||||
|
* @return 最新的构建记录
|
||||||
|
*/
|
||||||
|
Optional<JenkinsBuild> findTopByExternalSystemIdAndJobIdOrderByBuildNumberDesc(
|
||||||
|
Long externalSystemId, Long jobId);
|
||||||
}
|
}
|
||||||
@ -13,24 +13,6 @@ import com.qqchen.deploy.backend.deploy.query.JenkinsBuildQuery;
|
|||||||
*/
|
*/
|
||||||
public interface IJenkinsBuildService extends IBaseService<JenkinsBuild, JenkinsBuildDTO, JenkinsBuildQuery, Long> {
|
public interface IJenkinsBuildService extends IBaseService<JenkinsBuild, JenkinsBuildDTO, JenkinsBuildQuery, Long> {
|
||||||
|
|
||||||
/**
|
|
||||||
* 同步指定任务的构建信息
|
|
||||||
*
|
|
||||||
* @param externalSystem 外部系统
|
|
||||||
* @param job 任务
|
|
||||||
* @return 同步的构建数量
|
|
||||||
*/
|
|
||||||
Integer syncBuilds(ExternalSystem externalSystem, JenkinsJob job);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同步指定视图下所有任务的构建信息
|
|
||||||
*
|
|
||||||
* @param externalSystem 外部系统
|
|
||||||
* @param view 视图
|
|
||||||
* @return 同步的构建总数
|
|
||||||
*/
|
|
||||||
Integer syncBuildsByView(ExternalSystem externalSystem, JenkinsView view);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步外部系统下所有构建信息
|
* 同步外部系统下所有构建信息
|
||||||
*
|
*
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import com.qqchen.deploy.backend.deploy.repository.IJenkinsJobRepository;
|
|||||||
import com.qqchen.deploy.backend.deploy.repository.IJenkinsViewRepository;
|
import com.qqchen.deploy.backend.deploy.repository.IJenkinsViewRepository;
|
||||||
import com.qqchen.deploy.backend.deploy.service.IJenkinsBuildService;
|
import com.qqchen.deploy.backend.deploy.service.IJenkinsBuildService;
|
||||||
import com.qqchen.deploy.backend.deploy.service.IJenkinsSyncHistoryService;
|
import com.qqchen.deploy.backend.deploy.service.IJenkinsSyncHistoryService;
|
||||||
|
import com.qqchen.deploy.backend.deploy.service.sync.JenkinsSyncContext;
|
||||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||||
@ -78,84 +79,50 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public Integer syncAllBuilds(Long externalSystemId) {
|
public Integer syncAllBuilds(Long externalSystemId) {
|
||||||
// 1. 创建同步历史记录
|
// 1. 创建同步上下文
|
||||||
|
JenkinsSyncContext context = createSyncContext(externalSystemId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 2. 执行同步
|
||||||
|
return doSync(context);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 3. 处理异常
|
||||||
|
handleSyncException(context, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JenkinsSyncContext createSyncContext(Long externalSystemId) {
|
||||||
|
// 1. 查询外部系统
|
||||||
|
ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId)
|
||||||
|
.orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND));
|
||||||
|
|
||||||
|
// 2. 创建同步历史
|
||||||
JenkinsSyncHistoryDTO syncHistory = new JenkinsSyncHistoryDTO();
|
JenkinsSyncHistoryDTO syncHistory = new JenkinsSyncHistoryDTO();
|
||||||
syncHistory.setExternalSystemId(externalSystemId);
|
syncHistory.setExternalSystemId(externalSystemId);
|
||||||
syncHistory.setSyncType(JenkinsSyncType.BUILD);
|
syncHistory.setSyncType(JenkinsSyncType.BUILD);
|
||||||
syncHistory.setStatus(ExternalSystemSyncStatus.RUNNING);
|
syncHistory.setStatus(ExternalSystemSyncStatus.RUNNING);
|
||||||
jenkinsSyncHistoryService.saveOrUpdateHistory(syncHistory);
|
jenkinsSyncHistoryService.saveOrUpdateHistory(syncHistory);
|
||||||
|
|
||||||
try {
|
// 3. 返回同步上下文
|
||||||
// 2. 查询外部系统
|
return new JenkinsSyncContext(externalSystem, syncHistory);
|
||||||
ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId).orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND));
|
|
||||||
// 3. 查询所有视图
|
|
||||||
List<JenkinsView> views = jenkinsViewRepository.findByExternalSystemId(externalSystemId);
|
|
||||||
if (views.isEmpty()) {
|
|
||||||
log.info("No views found for external system: {}", externalSystemId);
|
|
||||||
// 更新同步历史为成功
|
|
||||||
syncHistory.setStatus(ExternalSystemSyncStatus.SUCCESS);
|
|
||||||
jenkinsSyncHistoryService.saveOrUpdateHistory(syncHistory);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// 4. 同步每个视图下的构建信息
|
|
||||||
int totalSyncedBuilds = 0;
|
|
||||||
StringBuilder errorMessages = new StringBuilder();
|
|
||||||
for (JenkinsView view : views) {
|
|
||||||
try {
|
|
||||||
Integer syncedBuilds = syncBuildsByView(externalSystem, view);
|
|
||||||
totalSyncedBuilds += syncedBuilds;
|
|
||||||
log.info("Successfully synchronized {} builds for view: {}", syncedBuilds, view.getViewName());
|
|
||||||
} catch (Exception e) {
|
|
||||||
String errorMessage = String.format("Failed to sync builds for view %s: %s", view.getViewName(), e.getMessage());
|
|
||||||
log.error(errorMessage, e);
|
|
||||||
errorMessages.append(errorMessage).append("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 更新同步历史状态
|
|
||||||
if (errorMessages.length() > 0) {
|
|
||||||
syncHistory.setStatus(ExternalSystemSyncStatus.FAILED);
|
|
||||||
syncHistory.setErrorMessage(errorMessages.toString());
|
|
||||||
} else {
|
|
||||||
syncHistory.setStatus(ExternalSystemSyncStatus.SUCCESS);
|
|
||||||
}
|
|
||||||
jenkinsSyncHistoryService.saveOrUpdateHistory(syncHistory);
|
|
||||||
|
|
||||||
log.info("Successfully synchronized total {} builds for external system: {}",
|
|
||||||
totalSyncedBuilds, externalSystemId);
|
|
||||||
return totalSyncedBuilds;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Failed to sync Jenkins builds for external system: {}", externalSystemId, e);
|
|
||||||
// 更新同步历史为失败
|
|
||||||
syncHistory.setStatus(ExternalSystemSyncStatus.FAILED);
|
|
||||||
syncHistory.setErrorMessage(e.getMessage());
|
|
||||||
jenkinsSyncHistoryService.saveOrUpdateHistory(syncHistory);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private Integer doSync(JenkinsSyncContext context) {
|
||||||
@Transactional(rollbackFor = Exception.class)
|
// 1. 获取所有视图
|
||||||
public Integer syncBuildsByView(ExternalSystem externalSystem, JenkinsView view) {
|
List<JenkinsView> views = jenkinsViewRepository.findByExternalSystemId(context.getExternalSystem().getId());
|
||||||
// 1. 查询视图下的所有任务
|
if (views.isEmpty()) {
|
||||||
List<JenkinsJob> jobs = jenkinsJobRepository.findByExternalSystemIdAndViewId(externalSystem.getId(), view.getId());
|
log.info("No views found for external system: {}", context.getExternalSystem().getId());
|
||||||
if (jobs.isEmpty()) {
|
updateSyncHistorySuccess(context.getSyncHistory());
|
||||||
log.info("No jobs found for view: {}", view.getId());
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 使用线程池并发同步每个任务的构建信息
|
// 2. 并发同步每个视图
|
||||||
List<CompletableFuture<Integer>> futures = jobs.stream()
|
List<CompletableFuture<Integer>> futures = views.stream()
|
||||||
.map(job -> CompletableFuture.supplyAsync(() -> {
|
.map(view -> CompletableFuture.supplyAsync(
|
||||||
try {
|
() -> syncView(context.getExternalSystem(), view),
|
||||||
Integer syncedBuilds = syncBuilds(externalSystem, job);
|
threadPoolTaskExecutor
|
||||||
log.info("Successfully synchronized {} builds for job: {}", syncedBuilds, job.getJobName());
|
))
|
||||||
return syncedBuilds;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Failed to sync builds for job: {}", job.getJobName(), e);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}, threadPoolTaskExecutor))
|
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
// 3. 等待所有任务完成并汇总结果
|
// 3. 等待所有任务完成并汇总结果
|
||||||
@ -171,67 +138,143 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
.mapToInt(Integer::intValue)
|
.mapToInt(Integer::intValue)
|
||||||
.sum();
|
.sum();
|
||||||
|
|
||||||
log.info("Successfully synchronized total {} builds for view: {}", totalSyncedBuilds, view.getViewName());
|
// 4. 更新同步历史
|
||||||
|
updateSyncHistorySuccess(context.getSyncHistory());
|
||||||
|
|
||||||
|
log.info("Successfully synchronized total {} builds for external system: {}",
|
||||||
|
totalSyncedBuilds, context.getExternalSystem().getId());
|
||||||
return totalSyncedBuilds;
|
return totalSyncedBuilds;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public Integer syncBuilds(ExternalSystem externalSystem, JenkinsJob job) {
|
protected Integer syncView(ExternalSystem externalSystem, JenkinsView view) {
|
||||||
JenkinsJobResponse queryJob = jenkinsServiceIntegration.job(externalSystem, job.getJobName());
|
try {
|
||||||
if (queryJob == null || queryJob.getLastBuild() == null) {
|
// 1. 获取视图下的所有任务
|
||||||
log.info("No builds found for job: {}", job.getJobName());
|
List<JenkinsJob> jobs = jenkinsJobRepository.findByExternalSystemIdAndViewId(
|
||||||
return 0;
|
externalSystem.getId(), view.getId());
|
||||||
}
|
|
||||||
|
if (jobs.isEmpty()) {
|
||||||
// Check if sync is needed
|
return 0;
|
||||||
if (job.getLastBuildNumber() != null && queryJob.getLastBuild().getNumber() <= job.getLastBuildNumber()) {
|
|
||||||
log.info("No new builds to sync for job: {}", job.getJobName());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<JenkinsBuildResponse> buildResponses = jenkinsServiceIntegration.listBuilds(externalSystem, job.getJobName());
|
|
||||||
if (buildResponses.isEmpty()) {
|
|
||||||
log.info("No builds found for job: {}", job.getJobName());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert and save/update build data
|
|
||||||
List<JenkinsBuild> jenkinsBuilds = new ArrayList<>();
|
|
||||||
for (JenkinsBuildResponse buildResponse : buildResponses) {
|
|
||||||
// Skip existing builds
|
|
||||||
if (job.getLastBuildNumber() != null && buildResponse.getNumber() <= job.getLastBuildNumber()) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new build
|
// 2. 同步每个任务
|
||||||
JenkinsBuild jenkinsBuild = new JenkinsBuild();
|
return jobs.stream()
|
||||||
jenkinsBuild.setExternalSystemId(externalSystem.getId());
|
.map(job -> syncJob(externalSystem, job))
|
||||||
jenkinsBuild.setJobId(job.getId());
|
.mapToInt(Integer::intValue)
|
||||||
jenkinsBuild.setBuildNumber(buildResponse.getNumber());
|
.sum();
|
||||||
updateBuildFromResponse(jenkinsBuild, buildResponse);
|
} catch (Exception e) {
|
||||||
jenkinsBuilds.add(jenkinsBuild);
|
log.error("Failed to sync view: {}", view.getViewName(), e);
|
||||||
log.debug("Creating new Jenkins build: {} for job: {}", jenkinsBuild.getBuildNumber(), job.getJobName());
|
return 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Batch save
|
@Transactional
|
||||||
if (!jenkinsBuilds.isEmpty()) {
|
protected Integer syncJob(ExternalSystem externalSystem, JenkinsJob job) {
|
||||||
jenkinsBuildRepository.saveAll(jenkinsBuilds);
|
try {
|
||||||
|
// 1. 获取任务最新状态
|
||||||
// Update job's last build info
|
JenkinsJobResponse jobResponse = jenkinsServiceIntegration.job(externalSystem, job.getJobName());
|
||||||
job.setLastBuildNumber(queryJob.getLastBuild().getNumber());
|
if (jobResponse == null || jobResponse.getLastBuild() == null) {
|
||||||
if (queryJob.getLastBuild().getTimestamp() != null) {
|
log.info("No build information available for job: {}", job.getJobName());
|
||||||
job.setLastBuildTime(LocalDateTime.ofInstant(
|
return 0;
|
||||||
Instant.ofEpochMilli(queryJob.getLastBuild().getTimestamp()),
|
|
||||||
ZoneId.systemDefault()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
jenkinsJobRepository.save(job);
|
|
||||||
|
|
||||||
log.info("Successfully synchronized {} builds for job: {}", jenkinsBuilds.size(), job.getJobName());
|
// 2. 获取数据库中最后一次构建记录
|
||||||
|
Optional<JenkinsBuild> lastBuild = jenkinsBuildRepository.findTopByExternalSystemIdAndJobIdOrderByBuildNumberDesc(
|
||||||
|
externalSystem.getId(), job.getId());
|
||||||
|
|
||||||
|
// 3. 获取需要同步的构建信息
|
||||||
|
List<JenkinsBuildResponse> newBuilds = getNewBuilds(externalSystem, job, jobResponse, lastBuild);
|
||||||
|
if (newBuilds.isEmpty()) {
|
||||||
|
log.info("No new builds to sync for job: {}", job.getJobName());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 保存新的构建信息
|
||||||
|
saveNewBuilds(externalSystem, job, newBuilds);
|
||||||
|
|
||||||
|
// 5. 更新任务的最新构建信息
|
||||||
|
updateJobLastBuild(job, jobResponse);
|
||||||
|
|
||||||
|
log.info("Successfully synchronized {} builds for job: {}", newBuilds.size(), job.getJobName());
|
||||||
|
return newBuilds.size();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to sync job: {}", job.getJobName(), e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<JenkinsBuildResponse> getNewBuilds(
|
||||||
|
ExternalSystem externalSystem,
|
||||||
|
JenkinsJob job,
|
||||||
|
JenkinsJobResponse jobResponse,
|
||||||
|
Optional<JenkinsBuild> lastBuild) {
|
||||||
|
// 获取所有构建
|
||||||
|
List<JenkinsBuildResponse> allBuilds = jenkinsServiceIntegration.listBuilds(
|
||||||
|
externalSystem, job.getJobName());
|
||||||
|
|
||||||
|
if (allBuilds.isEmpty()) {
|
||||||
|
return allBuilds;
|
||||||
}
|
}
|
||||||
|
|
||||||
return jenkinsBuilds.size();
|
// 如果是首次同步,获取所有构建
|
||||||
|
if (lastBuild.isEmpty()) {
|
||||||
|
log.info("First time sync for job: {}, will sync all builds", job.getJobName());
|
||||||
|
return allBuilds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取新的构建
|
||||||
|
Integer lastBuildNumber = lastBuild.get().getBuildNumber();
|
||||||
|
List<JenkinsBuildResponse> newBuilds = allBuilds.stream()
|
||||||
|
.filter(build -> build.getNumber() > lastBuildNumber)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
log.info("Found {} new builds for job: {} (last build number: {})",
|
||||||
|
newBuilds.size(), job.getJobName(), lastBuildNumber);
|
||||||
|
return newBuilds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveNewBuilds(
|
||||||
|
ExternalSystem externalSystem,
|
||||||
|
JenkinsJob job,
|
||||||
|
List<JenkinsBuildResponse> builds) {
|
||||||
|
List<JenkinsBuild> jenkinsBuilds = builds.stream()
|
||||||
|
.map(buildResponse -> {
|
||||||
|
JenkinsBuild build = new JenkinsBuild();
|
||||||
|
build.setExternalSystemId(externalSystem.getId());
|
||||||
|
build.setJobId(job.getId());
|
||||||
|
build.setBuildNumber(buildResponse.getNumber());
|
||||||
|
updateBuildFromResponse(build, buildResponse);
|
||||||
|
return build;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
log.info("Saving {} builds for job: {}", jenkinsBuilds.size(), job.getJobName());
|
||||||
|
jenkinsBuildRepository.saveAll(jenkinsBuilds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateJobLastBuild(JenkinsJob job, JenkinsJobResponse jobResponse) {
|
||||||
|
job.setLastBuildNumber(jobResponse.getLastBuild().getNumber());
|
||||||
|
if (jobResponse.getLastBuild().getTimestamp() != null) {
|
||||||
|
job.setLastBuildTime(LocalDateTime.ofInstant(
|
||||||
|
Instant.ofEpochMilli(jobResponse.getLastBuild().getTimestamp()),
|
||||||
|
ZoneId.systemDefault()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
jenkinsJobRepository.save(job);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSyncException(JenkinsSyncContext context, Exception e) {
|
||||||
|
log.error("Failed to sync Jenkins builds for external system: {}",
|
||||||
|
context.getExternalSystem().getId(), e);
|
||||||
|
|
||||||
|
context.getSyncHistory().setStatus(ExternalSystemSyncStatus.FAILED);
|
||||||
|
context.getSyncHistory().setErrorMessage(e.getMessage());
|
||||||
|
jenkinsSyncHistoryService.saveOrUpdateHistory(context.getSyncHistory());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSyncHistorySuccess(JenkinsSyncHistoryDTO syncHistory) {
|
||||||
|
syncHistory.setStatus(ExternalSystemSyncStatus.SUCCESS);
|
||||||
|
jenkinsSyncHistoryService.saveOrUpdateHistory(syncHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -242,7 +285,6 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
jenkinsBuild.setBuildStatus(response.getResult());
|
jenkinsBuild.setBuildStatus(response.getResult());
|
||||||
jenkinsBuild.setDuration(response.getDuration());
|
jenkinsBuild.setDuration(response.getDuration());
|
||||||
|
|
||||||
// 转换时间戳为LocalDateTime
|
|
||||||
if (response.getTimestamp() != null) {
|
if (response.getTimestamp() != null) {
|
||||||
LocalDateTime startTime = LocalDateTime.ofInstant(
|
LocalDateTime startTime = LocalDateTime.ofInstant(
|
||||||
Instant.ofEpochMilli(response.getTimestamp()),
|
Instant.ofEpochMilli(response.getTimestamp()),
|
||||||
@ -251,7 +293,6 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
jenkinsBuild.setStarttime(startTime);
|
jenkinsBuild.setStarttime(startTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将构建参数转换为JSON字符串
|
|
||||||
try {
|
try {
|
||||||
String actionsJson = objectMapper.writeValueAsString(response.getActions());
|
String actionsJson = objectMapper.writeValueAsString(response.getActions());
|
||||||
jenkinsBuild.setActions(actionsJson);
|
jenkinsBuild.setActions(actionsJson);
|
||||||
@ -264,6 +305,9 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
@Override
|
@Override
|
||||||
public Long countByExternalSystemId(Long externalSystemId) {
|
public Long countByExternalSystemId(Long externalSystemId) {
|
||||||
QJenkinsBuild qJenkinsBuild = QJenkinsBuild.jenkinsBuild;
|
QJenkinsBuild qJenkinsBuild = QJenkinsBuild.jenkinsBuild;
|
||||||
return super.repository.count(qJenkinsBuild.externalSystemId.eq(externalSystemId).and(qJenkinsBuild.deleted.eq(false)));
|
return super.repository.count(
|
||||||
|
qJenkinsBuild.externalSystemId.eq(externalSystemId)
|
||||||
|
.and(qJenkinsBuild.deleted.eq(false))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user