package com.caliverse.admin.domain.service; import com.caliverse.admin.domain.RabbitMq.MessageHandlerService; import com.caliverse.admin.domain.dao.admin.MenuMapper; import com.caliverse.admin.domain.entity.*; import com.caliverse.admin.domain.entity.log.LogAction; import com.caliverse.admin.domain.entity.log.LogStatus; import com.caliverse.admin.domain.request.MenuRequest; import com.caliverse.admin.domain.response.MenuResponse; import com.caliverse.admin.dynamodb.service.DynamodbMenuService; import com.caliverse.admin.global.common.annotation.BusinessProcess; import com.caliverse.admin.global.common.annotation.RequestLog; import com.caliverse.admin.global.common.code.CommonCode; import com.caliverse.admin.global.common.code.ErrorCode; import com.caliverse.admin.global.common.code.SuccessCode; import com.caliverse.admin.global.common.constants.CommonConstants; import com.caliverse.admin.global.common.constants.MysqlConstants; import com.caliverse.admin.global.common.exception.RestApiException; import com.caliverse.admin.global.common.utils.CommonUtils; import com.caliverse.admin.global.common.utils.FileUtils; import com.caliverse.admin.global.component.manager.BusinessProcessIdManager; import com.caliverse.admin.mongodb.service.MysqlHistoryLogService; import com.caliverse.admin.redis.service.RedisUserInfoService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.UrlResource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.web.multipart.MultipartFile; import software.amazon.awssdk.services.s3.model.S3Exception; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.nio.file.Path; import java.time.LocalDateTime; import java.util.HashMap; import java.util.List; import java.util.Map; @Slf4j @Service @RequiredArgsConstructor public class MenuService { @Value("${excel.file-path}") private String filePath; @Value("${caliverse.file}") private String samplePath; private final FileUtils fileUtils; private final MenuMapper menuMapper; private final ResourceLoader resourceLoader; private final MysqlHistoryLogService mysqlHistoryLogService; private final S3Service s3Service; private final DynamodbMenuService dynamodbMenuService; private final MessageHandlerService messageHandlerService; private final RedisUserInfoService redisUserInfoService; private final BusinessProcessIdManager processIdManager; @RequestLog public MenuResponse getList(Map requestParam){ //페이징 처리 requestParam = CommonUtils.pageSetting(requestParam); List list = menuMapper.getBannerList(requestParam); return MenuResponse.builder() .status(CommonCode.SUCCESS.getHttpStatus()) .result(CommonCode.SUCCESS.getResult()) .resultData(MenuResponse.ResultData.builder() .bannerList(list) .total(menuMapper.getTotal()) .totalAll(list.size()) .pageNo(requestParam.get("page_no")!=null? Integer.valueOf(requestParam.get("page_no").toString()):1) .build() ) .build(); } @RequestLog public MenuResponse getdetail(Long id){ MenuBanner banner = menuMapper.getBannerDetail(id); banner.setImageList(menuMapper.getMessage(id)); return MenuResponse.builder() .status(CommonCode.SUCCESS.getHttpStatus()) .result(CommonCode.SUCCESS.getResult()) .resultData(MenuResponse.ResultData.builder() .banner(banner) .build()) .build(); } public MenuResponse imageUpload(MultipartFile file){ if (file.isEmpty()) { log.error("File is empty"); throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_EXIT_FILE.toString()); } // 파일을 서버에 저장 String fileName = fileUtils.saveFile(file); return MenuResponse.builder() .resultData(MenuResponse.ResultData.builder() .message(SuccessCode.EXCEL_UPLOAD.getMessage()) .fileName(fileName) .build()) .status(CommonCode.SUCCESS.getHttpStatus()) .result(CommonCode.SUCCESS.getResult()) .build(); } public MenuResponse imageDelete(String fileName){ if (fileName.isEmpty()) { throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_EXIT_FILE.getMessage() ); } // 서버 파일 삭제 boolean is_delete = fileUtils.deleteFile(fileName); if(is_delete){ return MenuResponse.builder() .resultData(MenuResponse.ResultData.builder() .message(SuccessCode.FILE_DELETE.getMessage()) .build()) .status(CommonCode.SUCCESS.getHttpStatus()) .result(CommonCode.SUCCESS.getResult()) .build(); }else{ return MenuResponse.builder() .resultData(MenuResponse.ResultData.builder() .message(ErrorCode.ERROR_FILE_DELETE.getMessage()) .build()) .status(CommonCode.ERROR.getHttpStatus()) .result(CommonCode.ERROR.getResult()) .build(); } } public Resource fileDown(String fileName) { Path fileFullPath = Path.of(filePath+fileName); try { if(fileName.contains("sample")){ return resourceLoader.getResource(samplePath + fileName); } Resource resource = new UrlResource(fileFullPath.toUri()); if (resource.exists() && resource.isReadable()) { return resource; } else { throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_EXIT_EXCEL.getMessage()); } }catch (MalformedURLException e){ throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_EXIT_EXCEL.getMessage()); } } @BusinessProcess(action = LogAction.BANNER) @Transactional(transactionManager = "transactionManager") @RequestLog public MenuResponse postBanner(MenuRequest menuRequest){ menuRequest.setCreateBy(CommonUtils.getAdmin().getId()); int maxOrderId = menuMapper.getMaxOrderId(); menuRequest.setOrderId(maxOrderId+1); menuMapper.insertBanner(menuRequest); long banner_id = menuRequest.getId(); HashMap map = new HashMap<>(); map.put("id",String.valueOf(banner_id)); //메시지 저장(title=이미지명, content=이미지 링크) if(menuRequest.getImageList()!= null && !menuRequest.getImageList().isEmpty()){ menuRequest.getImageList().forEach(image -> { String temp_file = image.getContent(); String contentType = fileUtils.getContentType(temp_file); File file = fileUtils.loadFileObject(temp_file); String fileUri = ""; try{ String directory = String.format("%s/%s-%d", CommonConstants.S3_MENU_BANNER_DIRECTORY, CommonConstants.S3_MENU_BANNER_DIRECTORY, banner_id); fileUri = s3Service.uploadFile(file, directory, contentType); }catch (S3Exception e) { log.error("S3 오류: {}", e.getMessage()); throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.ERROR_FILE_S3_UPLOAD.getMessage()); } catch (IOException e) { log.error("파일 읽기 오류: {}", e.getMessage()); throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.ERROR_FILE_S3_UPLOAD.getMessage()); } catch (Exception e) { log.error("파일 업로드 중 예상치 못한 오류: {}", e.getMessage(), e); throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.ERROR_FILE_S3_UPLOAD.getMessage()); } if(fileUri.isEmpty()){ throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.ERROR_FILE_S3_UPLOAD.getMessage()); } map.put("title", fileUri); map.put("language", image.getLanguage()); if(menuRequest.isLink() && (menuRequest.getLinkList() != null && !menuRequest.getLinkList().isEmpty())){ String link = menuRequest.getLinkList().stream().filter(data -> data.getLanguage().equals(image.getLanguage())).findFirst().get().getContent(); map.put("content", link); }else{ map.put("content", ""); } menuMapper.insertMessage(map); fileUtils.deleteFile(temp_file); }); } log.info("postBanner Insert MenuBanner id: {}", menuRequest.getId()); MenuBanner banner = menuMapper.getBannerDetail(menuRequest.getId()); banner.setImageList(menuMapper.getMessage(menuRequest.getId())); String prodId = processIdManager.getCurrentProcessId(); LogAction action = processIdManager.getCurrentAction(); //message 정보까지 저장해야해서 수동 로그 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { try { mysqlHistoryLogService.insertHistoryLog( prodId, action, LogStatus.SUCCESS, MysqlConstants.TABLE_NAME_MENU_BANNER, "", banner ); } catch (Exception e) { log.warn("Failed to log banner creation: {}", e.getMessage()); } } }); if(redisUserInfoService.getAllServerList().isEmpty()){ throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_FOUND_SERVER.getMessage()); } dynamodbMenuService.insertBanner(banner); //운영DB 데이터 추가됐다고 게임서버 알림 notifyGameServers("postBanner", null); return MenuResponse.builder() .status(CommonCode.SUCCESS.getHttpStatus()) .result(CommonCode.SUCCESS.getResult()) .resultData(MenuResponse.ResultData.builder() .message(SuccessCode.SAVE.getMessage()) .build()) .build(); } @BusinessProcess(action = LogAction.BANNER) @Transactional(transactionManager = "transactionManager") @RequestLog public MenuResponse updateBanner(Long id, MenuRequest menuRequest) { menuRequest.setId(id); menuRequest.setUpdateBy(CommonUtils.getAdmin().getId()); menuRequest.setUpdateDt(LocalDateTime.now()); Long banner_id = menuRequest.getId(); MenuBanner before_info = menuMapper.getBannerDetail(banner_id); List before_msg = menuMapper.getMessage(banner_id); before_info.setImageList(before_msg); menuMapper.updateBanner(menuRequest); log.info("updateBanner Update Banner Complete: {}", menuRequest.getId()); MenuBanner after_info = menuMapper.getBannerDetail(banner_id); after_info.setImageList(menuMapper.getMessage(banner_id)); if(redisUserInfoService.getAllServerList().isEmpty()){ throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_FOUND_SERVER.getMessage()); } dynamodbMenuService.updateBanner(after_info); //운영DB 데이터 추가됐다고 게임서버 알림 notifyGameServers("updateBanner", null); return MenuResponse.builder() .status(CommonCode.SUCCESS.getHttpStatus()) .result(CommonCode.SUCCESS.getResult()) .resultData(MenuResponse.ResultData.builder() .message(SuccessCode.UPDATE.getMessage()) .build()) .build(); } @BusinessProcess(action = LogAction.BANNER) @Transactional(transactionManager = "transactionManager") @RequestLog public MenuResponse deleteBanner(Long id){ Map map = new HashMap<>(); map.put("id",id); MenuBanner banner = menuMapper.getBannerDetail(id); banner.setImageList(menuMapper.getMessage(id)); if(banner.getStartDt().isBefore(LocalDateTime.now())){ return MenuResponse.builder() .status(CommonCode.ERROR.getHttpStatus()) .result(CommonCode.ERROR.getResult()) .resultData(MenuResponse.ResultData.builder() .message(ErrorCode.START_DATE_OVER.getMessage()) .build()) .build(); } menuMapper.deleteBanner(map); menuMapper.deleteMessage(map); log.info("deleteBanner Delete Banner Complete id: {}", id); if(redisUserInfoService.getAllServerList().isEmpty()){ throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_FOUND_SERVER.getMessage()); } dynamodbMenuService.deleteBanner(banner.getId().intValue()); //게임서버 알림 notifyGameServers("deleteBanner", null); return MenuResponse.builder() .status(CommonCode.SUCCESS.getHttpStatus()) .result(CommonCode.SUCCESS.getResult()) .resultData(MenuResponse.ResultData.builder() .message(SuccessCode.DELETE.getMessage()) .build()) .build(); } public List getMessageList(Long id){ return menuMapper.getMessage(id); } private void notifyGameServers(String methodName, Runnable rollbackFunction) { // 운영DB 데이터 추가됐다고 게임서버 알림 List serverList = redisUserInfoService.getAllServerList(); if(serverList.isEmpty()){ log.error("{} serverList is empty", methodName); if (rollbackFunction != null) { rollbackFunction.run(); } throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_FOUND_SERVER.getMessage()); } try{ serverList.forEach(messageHandlerService::sendBannerMessage); } catch (Exception e) { log.error("{} messageHandlerService error: {}", methodName, e.getMessage(), e); if (rollbackFunction != null) { rollbackFunction.run(); } throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.MESSAGE_SEND_FAIL.getMessage()); } } }