Initial commit

This commit is contained in:
jooho
2021-10-20 17:12:00 +09:00
parent 0c884beff8
commit 8caa4bbc5a
487 changed files with 44198 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
package org.egovframe.cloud.reservechecksevice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;
import reactivefeign.spring.config.EnableReactiveFeignClients;
import reactor.blockhound.BlockHound;
import java.security.Security;
@ComponentScan({"org.egovframe.cloud.common", "org.egovframe.cloud.reactive", "org.egovframe.cloud.reservechecksevice"}) // org.egovframe.cloud.common package 포함하기 위해
@EnableDiscoveryClient
@EnableReactiveFeignClients
@SpringBootApplication
public class ReserveCheckSeviceApplication {
public static void main(String[] args) {
// TLSv1/v1.1 No longer works after upgrade, "No appropriate protocol" error
String property = Security.getProperty("jdk.tls.disabledAlgorithms").replace(", TLSv1", "").replace(", TLSv1.1", "");
Security.setProperty("jdk.tls.disabledAlgorithms", property);
//blocking 코드 감지
BlockHound.builder()
//mysql r2dbc 에서 호출되는 FileInputStream.readBytes() 가 블로킹코드인데 이를 허용해주도록 한다.
//해당 코드가 어디서 호출되는지 알지 못하는 상태에서 FileInputStream.readBytes() 자체를 허용해주는 것은 좋지 않다.
// 누군가 무분별하게 사용하게 되면 검출해 낼 수ㅂ 없어 시스템의 위험요소로 남게 된다.
// r2dbc를 사용하기 위해 해당 호출부분만 허용하고 나머지는 여전히 검출대상으로 남기도록 한다.
.allowBlockingCallsInside("dev.miku.r2dbc.mysql.client.ReactorNettyClient", "init")
.install();
SpringApplication.run(ReserveCheckSeviceApplication.class, args);
}
}

View File

@@ -0,0 +1,174 @@
package org.egovframe.cloud.reservechecksevice.api.reserve;
import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.reservechecksevice.api.reserve.dto.*;
import org.egovframe.cloud.reservechecksevice.domain.reserve.Category;
import org.egovframe.cloud.reservechecksevice.service.reserve.ReserveService;
import org.springframework.core.env.Environment;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.validation.Valid;
/**
* org.egovframe.cloud.reservechecksevice.api.reserve.ReserveApiController
* <p>
* 예약 확인 rest controller class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/17
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/17 shinmj 최초 생성
* </pre>
*/
@RequiredArgsConstructor
@RestController
public class ReserveApiController {
private final ReserveService reserveService;
private final Environment env;
/**
* 서비스 상태 확인
*
* @return
*/
@GetMapping("/actuator/health-info")
public String status() {
return String.format("GET Reserve Check Service on" +
"\n local.server.port :" + env.getProperty("local.server.port")
+ "\n egov.message :" + env.getProperty("egov.message")
);
}
/**
* 예약 확인(신청) 목록 조회
* 관리자인 경우 모두 조회
*
* @param requestDto
* @param page
* @param size
* @return
*/
@GetMapping("/api/v1/reserves")
@ResponseStatus(HttpStatus.OK)
public Mono<Page<ReserveListResponseDto>> search(ReserveRequestDto requestDto,
@RequestParam(name = "page") int page,
@RequestParam(name = "size") int size) {
return reserveService.search(requestDto, PageRequest.of(page, size));
}
/**
* 사용자별 예약 목록 조회
*
* @param userId
* @param requestDto
* @param page
* @param size
* @return
*/
@GetMapping("/api/v1/{userId}/reserves")
@ResponseStatus(HttpStatus.OK)
public Mono<Page<ReserveListResponseDto>> searchForUser(@PathVariable String userId,
ReserveRequestDto requestDto,
@RequestParam(name = "page") int page,
@RequestParam(name = "size") int size) {
return reserveService.searchForUser(userId, requestDto, PageRequest.of(page, size));
}
/**
* 예약 한건 조회
*
* @param reserveId
* @return
*/
@GetMapping("/api/v1/reserves/{reserveId}")
@ResponseStatus(HttpStatus.OK)
public Mono<ReserveResponseDto> findById(@PathVariable String reserveId) {
return reserveService.findReserveById(reserveId);
}
/**
* 예약 취소
*
* @param reserveId
* @return
*/
@PutMapping("/api/v1/reserves/cancel/{reserveId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<Void> cancel(@PathVariable String reserveId, @RequestBody ReserveCancelRequestDto cancelRequestDto) {
return reserveService.cancel(reserveId, cancelRequestDto);
}
/**
* 예약 승인
*
* @param reserveId
* @return
*/
@PutMapping("/api/v1/reserves/approve/{reserveId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<Void> approve(@PathVariable String reserveId) {
return reserveService.approve(reserveId);
}
/**
* 예약 정보 수정
*
* @param reserveId
* @param updateRequestDto
* @return
*/
@PutMapping("/api/v1/reserves/{reserveId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<Void> update(@PathVariable String reserveId, @Valid @RequestBody ReserveUpdateRequestDto updateRequestDto) {
return reserveService.update(reserveId, updateRequestDto).then();
}
/**
* 관리자 예약 신청
* 관리자의 경우 실시간이어도 이벤트 스트림 거치지 않고 바로 예약 처리
*
* @param saveRequestDto
* @return
*/
@PostMapping("/api/v1/reserves")
@ResponseStatus(HttpStatus.CREATED)
public Mono<ReserveResponseDto> create(@Valid @RequestBody ReserveSaveRequestDto saveRequestDto) {
return reserveService.create(saveRequestDto);
}
/**
* 예약물품 별 조회기간 내 예약 목록 조회
*
* @param reserveItemId
* @return
*/
@GetMapping("/api/v1/reserves/{reserveItemId}/inventories")
@ResponseStatus(HttpStatus.OK)
public Mono<Integer> countInventory(@PathVariable Long reserveItemId,
@RequestParam(name = "startDate") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
@RequestParam(name = "endDate") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
return reserveService.countInventory(reserveItemId,startDate.atTime(1,1), endDate.atTime(1,1));
}
}

View File

@@ -0,0 +1,38 @@
package org.egovframe.cloud.reservechecksevice.api.reserve.dto;
import javax.validation.constraints.NotBlank;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* org.egovframe.cloud.reservechecksevice.api.reserve.dto.ReserveCancelRequestDto
* <p>
* 예약 취소 요청 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/10/06
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/10/06 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
public class ReserveCancelRequestDto {
@NotBlank
private String reasonCancelContent;
@Builder
public ReserveCancelRequestDto(String reasonCancelContent) {
this.reasonCancelContent = reasonCancelContent;
}
}

View File

@@ -0,0 +1,65 @@
package org.egovframe.cloud.reservechecksevice.api.reserve.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.egovframe.cloud.reservechecksevice.domain.reserve.Reserve;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reservechecksevice.api.reserve.dto.ReserveListResponseDto
* <p>
* 예약 목록 응답 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/17
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/17 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
public class ReserveListResponseDto {
private String reserveId;
private Long locationId;
private String categoryId;
private Long reserveItemId;
private String reserveItemName;
private Integer totalQty;
private Integer reserveQty;
private String userId;
private String userName;
private String reserveStatusId;
private LocalDateTime createDate;
@Builder
public ReserveListResponseDto(Reserve entity) {
this.reserveId = entity.getReserveId();
this.locationId = entity.getLocationId();
this.categoryId = entity.getCategoryId();
this.reserveItemId = entity.getReserveItemId();
this.reserveItemName = entity.getReserveItem().getReserveItemName();
this.totalQty = entity.getReserveItem().getTotalQty();
this.reserveQty = entity.getReserveQty();
this.userId = entity.getUserId();
this.userName = entity.getUser().getUserName();
this.reserveStatusId = entity.getReserveStatusId();
this.createDate = entity.getCreateDate();
}
}

View File

@@ -0,0 +1,29 @@
package org.egovframe.cloud.reservechecksevice.api.reserve.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.egovframe.cloud.common.dto.RequestDto;
/**
* org.egovframe.cloud.reservechecksevice.api.reserve.dto.ReserveRequestDto
* <p>
* 얘약 목록 요청 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/27
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/27 shinmj 최초 생성
* </pre>
*/
@NoArgsConstructor
@Getter
public class ReserveRequestDto extends RequestDto {
private Long locationId;
private String categoryId;
}

View File

@@ -0,0 +1,69 @@
package org.egovframe.cloud.reservechecksevice.api.reserve.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.egovframe.cloud.reservechecksevice.client.dto.ReserveItemRelationResponseDto;
import org.egovframe.cloud.reservechecksevice.domain.reserve.Reserve;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reservechecksevice.api.reserve.dto.ReserveResponseDto
* <p>
* 예약 확인(신청) 응답 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/17
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/17 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
public class ReserveResponseDto {
private String reserveId;
private Long reserveItemId;
private ReserveItemRelationResponseDto reserveItem;
private Integer reserveQty;
private LocalDateTime reserveStartDate;
private LocalDateTime reserveEndDate;
private String reservePurposeContent;
private String attachmentCode;
private String reserveStatusId;
private String userId;
private String userName;
private String userContactNo;
private String userEmail;
@Builder
public ReserveResponseDto(Reserve entity) {
this.reserveId = entity.getReserveId();
this.reserveItemId = entity.getReserveItemId();
this.reserveItem = ReserveItemRelationResponseDto.builder().entity(entity.getReserveItem()).build();
this.reserveQty = entity.getReserveQty();
this.reserveStartDate = entity.getReserveStartDate();
this.reserveEndDate = entity.getReserveEndDate();
this.reservePurposeContent = entity.getReservePurposeContent();
this.attachmentCode = entity.getAttachmentCode();
this.reserveStatusId = entity.getReserveStatusId();
this.userId = entity.getUserId();
this.userName = entity.getUser().getUserName();
this.userContactNo = entity.getUserContactNo();
this.userEmail = entity.getUserEmail();
}
}

View File

@@ -0,0 +1,99 @@
package org.egovframe.cloud.reservechecksevice.api.reserve.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.With;
import org.egovframe.cloud.reservechecksevice.domain.reserve.Reserve;
import org.egovframe.cloud.reservechecksevice.validator.annotation.ReserveSaveValid;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reservechecksevice.api.reserve.dto.ReserveSaveRequestDto
* <p>
* 예약 신청 요청 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/17
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/17 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
@ReserveSaveValid
public class ReserveSaveRequestDto {
@Setter
private String reserveId;
@NotNull
private Long reserveItemId;
private Long locationId;
private String categoryId;
private Integer reserveQty; //예약 신청 인원/수량
@NotNull
private String reservePurposeContent; //예약 목적
private String attachmentCode; //첨부파일 코드
private LocalDateTime reserveStartDate; //예약 신청 시작일
private LocalDateTime reserveEndDate; //예약 신청 종료일
private String reserveStatusId; //예약상태 - 공통코드(reserve-status)
@NotNull
private String userId; //예약자
@NotNull
private String userContactNo; //예약자 연락처
@NotNull
private String userEmail; //예약자 이메일
@Builder
public ReserveSaveRequestDto(Long reserveItemId, Long locationId, String categoryId, Integer reserveQty, String reservePurposeContent, String attachmentCode, LocalDateTime reserveStartDate, LocalDateTime reserveEndDate, String reserveStatusId, String userId, String userContactNo, String userEmail) {
this.reserveItemId = reserveItemId;
this.locationId = locationId;
this.categoryId = categoryId;
this.reserveQty = reserveQty;
this.reservePurposeContent = reservePurposeContent;
this.attachmentCode = attachmentCode;
this.reserveStartDate = reserveStartDate;
this.reserveEndDate = reserveEndDate;
this.reserveStatusId = reserveStatusId;
this.userId = userId;
this.userContactNo = userContactNo;
this.userEmail = userEmail;
}
public Reserve toEntity() {
Reserve reserve = Reserve.builder()
.reserveId(this.reserveId)
.reserveItemId(this.reserveItemId)
.reserveQty(this.reserveQty)
.reservePurposeContent(this.reservePurposeContent)
.attachmentCode(this.attachmentCode)
.reserveStartDate(this.reserveStartDate)
.reserveEndDate(this.reserveEndDate)
.reserveStatusId(this.reserveStatusId)
.userId(this.userId)
.userContactNo(this.userContactNo)
.userEmail(this.userEmail)
.build();
reserve.setLocationId(this.locationId);
reserve.setCategoryId(this.categoryId);
return reserve;
}
}

View File

@@ -0,0 +1,68 @@
package org.egovframe.cloud.reservechecksevice.api.reserve.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.egovframe.cloud.reservechecksevice.validator.annotation.ReserveSaveValid;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reservechecksevice.api.reserve.dto.ReserveUpdateRequestDto
* <p>
* 예약 신청 정보 수정 요청 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/17
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/17 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
@ReserveSaveValid
public class ReserveUpdateRequestDto {
@NotNull
private Long reserveItemId;
private String categoryId;
private Integer reserveQty; //예약 신청 인원/수량
@NotNull
private String reservePurposeContent; //예약 목적
private String attachmentCode; //첨부파일 코드
private LocalDateTime reserveStartDate; //예약 신청 시작일
private LocalDateTime reserveEndDate; //예약 신청 종료일
@NotNull
private String userId; //예약자
@NotNull
private String userContactNo; //예약자 연락처
@NotNull
private String userEmail; //예약자 이메일
@Builder
public ReserveUpdateRequestDto(Long reserveItemId, String categoryId, Integer reserveQty, String reservePurposeContent, String attachmentCode, LocalDateTime reserveStartDate, LocalDateTime reserveEndDate, String userId, String userContactNo, String userEmail) {
this.reserveItemId = reserveItemId;
this.categoryId = categoryId;
this.reserveQty = reserveQty;
this.reservePurposeContent = reservePurposeContent;
this.attachmentCode = attachmentCode;
this.reserveStartDate = reserveStartDate;
this.reserveEndDate = reserveEndDate;
this.userId = userId;
this.userContactNo = userContactNo;
this.userEmail = userEmail;
}
}

View File

@@ -0,0 +1,59 @@
package org.egovframe.cloud.reservechecksevice.client;
import org.egovframe.cloud.reservechecksevice.client.dto.ReserveItemRelationResponseDto;
import org.egovframe.cloud.reservechecksevice.client.dto.ReserveItemResponseDto;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import reactivefeign.spring.config.ReactiveFeignClient;
import reactor.core.publisher.Mono;
/**
* org.egovframe.cloud.reservechecksevice.client.ReserveItemServiceClient
* <p>
* 예약 물품 서비스와 통신하는 feign client interface
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/23
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/23 shinmj 최초 생성
* </pre>
*/
@ReactiveFeignClient(value = "reserve-item-service")
public interface ReserveItemServiceClient {
/**
* 예약 물품 한건 조회
*
* @param reserveItemId
* @return
*/
@GetMapping("/api/v1/reserve-items/{reserveItemId}")
Mono<ReserveItemResponseDto> findById(@PathVariable("reserveItemId") Long reserveItemId);
/**
* 예약 물품 한건 조회 시 연결된 공통코드, 지역 정보 조회
*
* @param reserveItemId
* @return
*/
@GetMapping("/api/v1/reserve-items/relations/{reserveItemId}")
Mono<ReserveItemRelationResponseDto> findByIdWithRelations(@PathVariable("reserveItemId") Long reserveItemId);
/**
* 관리자가 예약 신청 시 이벤트 스트림 없이 바로 재고 변경
*
* @param reserveItemId
* @param reserveQty
* @return
*/
@PutMapping("/api/v1/reserve-items/{reserveItemId}/inventories")
Mono<Boolean> updateInventory(@PathVariable("reserveItemId") Long reserveItemId, @RequestBody Integer reserveQty);
}

View File

@@ -0,0 +1,39 @@
package org.egovframe.cloud.reservechecksevice.client;
import org.egovframe.cloud.reservechecksevice.client.dto.UserResponseDto;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import reactivefeign.spring.config.ReactiveFeignClient;
import reactor.core.publisher.Mono;
/**
* org.egovframe.cloud.reservechecksevice.client.UserServiceClient
* <p>
* 사용자 서비스와 통신하는 feign client interface
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/30
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/30 shinmj 최초 생성
* </pre>
*/
@ReactiveFeignClient(value = "user-service")
public interface UserServiceClient {
/**
* 사용자 단 건 조회
*
* @param userId
* @return
*/
@GetMapping("/api/v1/users/{userId}")
Mono<UserResponseDto> findByUserId(@PathVariable("userId") String userId);
}

View File

@@ -0,0 +1,147 @@
package org.egovframe.cloud.reservechecksevice.client.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.egovframe.cloud.reservechecksevice.domain.location.Location;
import org.egovframe.cloud.reservechecksevice.domain.reserve.ReserveItem;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reservechecksevice.client.dto.ReserveItemRelationResponseDto
* <p>
* 얘약 물품 feign client 응답 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/27
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/27 shinmj 최초 생성
* </pre>
*/
@NoArgsConstructor
@AllArgsConstructor
@Getter
@ToString
public class ReserveItemRelationResponseDto {
private Long reserveItemId; // 예약 물품 id
private String reserveItemName; //예약 물품 명
private Long locationId;
private Location location;
private String categoryId; //예약유형 - 공통코드 reserve-category
private String categoryName;
private Integer totalQty; //총 재고/수용인원 수
private Integer inventoryQty; // 재고/수용인원 수
private LocalDateTime operationStartDate; //운영 시작 일
private LocalDateTime operationEndDate; //운영 종료 일
private String reserveMethodId; // 예약 방법 - 공통코드 reserve-method
private String reserveMethodName;
private String reserveMeansId; // 예약 구분 (인터넷 예약 시) - 공통코드 reserve-means
private String reserveMeansName;
private LocalDateTime requestStartDate; //예약 신청 시작 일시
private LocalDateTime requestEndDate; //예약 신청 종료 일시
private Boolean isPeriod; //기간 지정 가능 여부 - true: 지정 가능, false: 지정 불가
private Integer periodMaxCount; // 최대 예약 가능 일 수
private String externalUrl; //외부링크
private String selectionMeansId; //선별 방법 - 공통코드 reserve-selection
private String selectionMeansName;
private Boolean isPaid; // 유/무료 - false: 무료, true: 유료
private BigDecimal usageCost; //이용 요금
private Boolean isUse; //사용여부
private String purpose; //용도
private String address; //주소
private String targetId; //이용 대상 - 공통코드 reserve-target
private String targetName;
private String excluded; // 사용허가 제외대상
private String homepage; //홈페이지 주소
private String contact; //문의처
private String managerDept; //담당자 소속
private String managerName; //담당자 이름
private String managerContact; //담당자 연락처
@Builder
public ReserveItemRelationResponseDto(ReserveItem entity) {
this.reserveItemId = entity.getReserveItemId();
this.reserveItemName = entity.getReserveItemName();
this.locationId = entity.getLocationId();
this.location = entity.getLocation();
this.categoryId = entity.getCategoryId();
this.categoryName = entity.getCategoryName();
this.totalQty = entity.getTotalQty();
this.inventoryQty = entity.getInventoryQty();
this.operationStartDate = entity.getOperationStartDate();
this.operationEndDate = entity.getOperationEndDate();
this.reserveMethodId = entity.getReserveMethodId();
this.reserveMethodName = entity.getReserveMethodName();
this.reserveMeansId = entity.getReserveMeansId();
this.reserveMeansName = entity.getReserveMeansName();
this.requestStartDate = entity.getRequestStartDate();
this.requestEndDate = entity.getRequestEndDate();
this.isPeriod = entity.getIsPeriod();
this.periodMaxCount = entity.getPeriodMaxCount();
this.externalUrl = entity.getExternalUrl();
this.selectionMeansId = entity.getSelectionMeansId();
this.selectionMeansName = entity.getSelectionMeansName();
this.isPaid = entity.getIsPaid();
this.usageCost = entity.getUsageCost();
this.isUse = entity.getIsUse();
this.purpose = entity.getPurpose();
this.address = entity.getAddress();
this.targetId = entity.getTargetId();
this.targetName = entity.getTargetName();
this.excluded = entity.getExcluded();
this.homepage = entity.getHomepage();
this.contact = entity.getContact();
this.managerDept = entity.getManagerDept();
this.managerName = entity.getManagerName();
this.managerContact = entity.getManagerContact();
}
public ReserveItem toEntity() {
return ReserveItem.builder()
.reserveItemName(this.reserveItemName)
.locationId(this.locationId)
.location(this.location)
.categoryId(this.categoryId)
.categoryName(this.categoryName)
.totalQty(this.totalQty)
.inventoryQty(this.inventoryQty)
.operationStartDate(this.operationStartDate)
.operationEndDate(this.operationEndDate)
.reserveMethodId(this.reserveMethodId)
.reserveMethodName(this.reserveMethodName)
.reserveMeansId(this.reserveMeansId)
.reserveMeansName(this.reserveMeansName)
.requestStartDate(this.requestStartDate)
.requestEndDate(this.requestEndDate)
.isPeriod(this.isPeriod)
.periodMaxCount(this.periodMaxCount)
.externalUrl(this.externalUrl)
.selectionMeansId(this.selectionMeansId)
.selectionMeansName(this.selectionMeansName)
.isPaid(this.isPaid)
.usageCost(this.usageCost)
.isUse(this.isUse)
.purpose(this.purpose)
.address(this.address)
.targetId(this.targetId)
.targetName(this.targetName)
.excluded(this.excluded)
.homepage(this.homepage)
.contact(this.contact)
.managerDept(this.managerDept)
.managerName(this.managerName)
.managerContact(this.managerContact)
.build();
}
}

View File

@@ -0,0 +1,93 @@
package org.egovframe.cloud.reservechecksevice.client.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.egovframe.cloud.reservechecksevice.domain.reserve.ReserveItem;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reserveitemservice.api.reserveItem.dto.ReserveItemResponseDto
* <p>
* 예약 물품 응답 dto class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/13
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/13 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
public class ReserveItemResponseDto {
private Long reserveItemId; // 예약 물품 id
private String reserveItemName; //예약 물품 명
private Long locationId;
private String categoryId; //예약유형 - 공통코드 reserve-category
private Integer totalQty; //총 재고/수용인원 수
private Integer inventoryQty; // 재고/수용인원 수
private LocalDateTime operationStartDate; //운영 시작 일
private LocalDateTime operationEndDate; //운영 종료 일
private String reserveMethodId; // 예약 방법 - 공통코드 reserve-method
private String reserveMeansId; // 예약 구분 (인터넷 예약 시) - 공통코드 reserve-means
private LocalDateTime requestStartDate; //예약 신청 시작 일시
private LocalDateTime requestEndDate; //예약 신청 종료 일시
private Boolean isPeriod; //기간 지정 가능 여부 - true: 지정 가능, false: 지정 불가
private Integer periodMaxCount; // 최대 예약 가능 일 수
private String externalUrl; //외부링크
private String selectionMeansId; //선별 방법 - 공통코드 reserve-selection
private Boolean isPaid; // 유/무료 - true: 무료, false: 유료
private BigDecimal usageCost; //이용 요금
private Boolean isUse; //사용여부
private String purpose; //용도
private String address; //주소
private String targetId; //이용 대상 - 공통코드 reserve-target
private String excluded; // 사용허가 제외대상
private String homepage; //홈페이지 주소
private String contact; //문의처
private String managerDept; //담당자 소속
private String managerName; //담당자 이름
private String managerContact; //담당자 연락처
@Builder
public ReserveItemResponseDto(ReserveItem reserveItem) {
this.reserveItemId = reserveItem.getReserveItemId();
this.reserveItemName = reserveItem.getReserveItemName();
this.locationId = reserveItem.getLocationId();
this.categoryId = reserveItem.getCategoryId();
this.totalQty = reserveItem.getTotalQty();
this.inventoryQty = reserveItem.getInventoryQty();
this.operationStartDate = reserveItem.getOperationStartDate();
this.operationEndDate = reserveItem.getOperationEndDate();
this.reserveMethodId = reserveItem.getReserveMethodId();
this.reserveMeansId = reserveItem.getReserveMeansId();
this.requestStartDate = reserveItem.getRequestStartDate();
this.requestEndDate = reserveItem.getRequestEndDate();
this.isPeriod = reserveItem.getIsPeriod();
this.periodMaxCount = reserveItem.getPeriodMaxCount();
this.externalUrl = reserveItem.getExternalUrl();
this.selectionMeansId = reserveItem.getSelectionMeansId();
this.isPaid = reserveItem.getIsPaid();
this.usageCost = reserveItem.getUsageCost();
this.isUse = reserveItem.getIsUse();
this.purpose = reserveItem.getPurpose();
this.address = reserveItem.getAddress();
this.targetId = reserveItem.getTargetId();
this.excluded = reserveItem.getExcluded();
this.homepage = reserveItem.getHomepage();
this.contact = reserveItem.getContact();
this.managerDept = reserveItem.getManagerDept();
this.managerName = reserveItem.getManagerName();
this.managerContact = reserveItem.getManagerContact();
}
}

View File

@@ -0,0 +1,50 @@
package org.egovframe.cloud.reservechecksevice.client.dto;
import lombok.Builder;
import lombok.Getter;
/**
* org.egovframe.cloud.userservice.api.user.dto.UserResponseDto
* <p>
* 사용자 정보 요청시 사용되는 필요한 정보만 담긴 DTO
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/06/30
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/06/30 jaeyeolkim 최초 생성
* </pre>
*/
@Getter
public class UserResponseDto {
private String userId;
private String userName;
private String email;
private String roleId;
private String userStateCode;
private String googleId;
private String kakaoId;
private String naverId;
private Boolean isSocialUser;
private Boolean hasPassword;
@Builder
public UserResponseDto(String userId, String userName, String email, String roleId, String userStateCode, String googleId, String kakaoId, String naverId, Boolean isSocialUser, Boolean hasPassword) {
this.userId = userId;
this.userName = userName;
this.email = email;
this.roleId = roleId;
this.userStateCode = userStateCode;
this.googleId = googleId;
this.kakaoId = kakaoId;
this.naverId = naverId;
this.isSocialUser = isSocialUser;
this.hasPassword = hasPassword;
}
}

View File

@@ -0,0 +1,45 @@
package org.egovframe.cloud.reservechecksevice.config;
import java.time.Duration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
/**
* org.egovframe.cloud.portalservice.config.Resilience4JConfig
* <p>
* Resilience4J Configuration
* 기본 설정값으로 운영되어도 무방하다. 이 클래스는 필수는 아니다.
* retry 기본값은 최대 3회이고, fallback 이 없는 경우에만 동작하므로 설정하지 않았다.
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/08/31
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/08/31 jaeyeolkim 최초 생성
* 2021/10/05 shinmj reactive로 변경
* </pre>
*/
@Configuration
public class Resilience4JConfig {
@Bean
public CircuitBreakerRegistry circuitBreakerRegistry() {
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // Circuit 열지 말지 결정하는 실패 threshold 퍼센테이지
.waitDurationInOpenState(Duration.ofSeconds(5)) // (half closed 전에) circuitBreaker가 open 되기 전에 기다리는 기간
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED) // circuit breaker count 기반 처리
.slidingWindowSize(10) // 통계 대상 건수 -> N건의 요청중..
.build();
return CircuitBreakerRegistry.of(circuitBreakerConfig);
}
}

View File

@@ -0,0 +1,52 @@
package org.egovframe.cloud.reservechecksevice.domain.location;
import lombok.*;
import org.egovframe.cloud.reactive.domain.BaseEntity;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
import javax.validation.constraints.Size;
/**
* org.egovframe.cloud.reserveitemservice.domain.location.Location
*
* 예약 지역 도메인 클래스
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/06
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/06 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
public class Location extends BaseEntity {
@Id
private Long locationId;
@Size(max = 200)
@Column
private String locationName;
@Column
private Integer sortSeq;
@Column("use_at")
private Boolean isUse;
@Builder
public Location(Long locationId, String locationName, Integer sortSeq, Boolean isUse) {
this.locationId = locationId;
this.locationName = locationName;
this.sortSeq = sortSeq;
this.isUse = isUse;
}
}

View File

@@ -0,0 +1,36 @@
package org.egovframe.cloud.reservechecksevice.domain.reserve;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* org.egovframe.cloud.reservechecksevice.domain.reserve.Category
*
* 예약 유형 enum class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/15 shinmj 최초 생성
* </pre>
*/
@Getter
@RequiredArgsConstructor
public enum Category {
EDUCATION("education", "교육"),
EQUIPMENT("equipment", "장비"),
SPACE("space", "공간");
private final String key;
private final String title;
public boolean isEquals(String compare) {
return this.getKey().equals(compare);
}
}

View File

@@ -0,0 +1,203 @@
package org.egovframe.cloud.reservechecksevice.domain.reserve;
import lombok.*;
import org.egovframe.cloud.reactive.domain.BaseEntity;
import org.egovframe.cloud.reservechecksevice.api.reserve.dto.ReserveUpdateRequestDto;
import org.egovframe.cloud.reservechecksevice.client.dto.UserResponseDto;
import org.egovframe.cloud.reservechecksevice.domain.location.Location;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reservechecksevice.domain.reserve.Reserve
*
* 예약 도메인 클래스
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/15 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
@With
@Table("reserve")
public class Reserve extends BaseEntity {
@Id
@Column
private String reserveId; //예약 id
@Column
private Long reserveItemId; //예약 물품 id
@Transient
private ReserveItem reserveItem;
@Setter
@Column
private Long locationId; //지역 id
@Setter
@Column
private String categoryId; //예약유형 - 공통코드 reserve-category
@Column
private Integer reserveQty; //예약 신청 인원/수량
@Column
private String reservePurposeContent; //예약 목적
@Column
private String attachmentCode; //첨부파일 코드
@Column
private LocalDateTime reserveStartDate; //예약 신청 시작일
@Column
private LocalDateTime reserveEndDate; //예약 신청 종료일
@Column
private String reserveStatusId; //예약상태 - 공통코드(reserve-status)
@Column
private String reasonCancelContent; //예약 취소 사유
@Column
private String userId; //예약자
@Transient
private UserResponseDto user;
@Column
private String userContactNo; //예약자 연락처
@Column("user_email_addr")
private String userEmail; //예약자 이메일
@Builder
public Reserve(String reserveId, Long reserveItemId,
ReserveItem reserveItem, Long locationId, String categoryId, Integer reserveQty,
String reservePurposeContent, String attachmentCode, LocalDateTime reserveStartDate,
LocalDateTime reserveEndDate, String reserveStatusId, String reasonCancelContent, String userId,
UserResponseDto user, String userContactNo, String userEmail) {
this.reserveId = reserveId;
this.reserveItemId = reserveItemId;
this.reserveItem = reserveItem;
this.locationId = locationId;
this.categoryId = categoryId;
this.reserveQty = reserveQty;
this.reservePurposeContent = reservePurposeContent;
this.attachmentCode = attachmentCode;
this.reserveStartDate = reserveStartDate;
this.reserveEndDate = reserveEndDate;
this.reserveStatusId = reserveStatusId;
this.reasonCancelContent = reasonCancelContent;
this.userId = userId;
this.user = user;
this.userContactNo = userContactNo;
this.userEmail = userEmail;
}
/**
* 물품 정보 세팅
*
* @param reserveItem
* @return
*/
public Reserve setReserveItem(ReserveItem reserveItem) {
this.reserveItem = reserveItem;
this.reserveItemId = reserveItem.getReserveItemId();
return this;
}
/**
* 예약자 정보 세팅
*
* @param user
* @return
*/
public Reserve setUser(UserResponseDto user) {
this.user = user;
this.userId = user.getUserId();
return this;
}
/**
* 예약 상태 업데이트
*
* @param reserveStatusId
* @return
*/
public Reserve updateStatus(String reserveStatusId) {
this.reserveStatusId = reserveStatusId;
return this;
}
/**
* 취소 사유 업데이트
*
* @param reasonCancelContent
* @return
*/
public Reserve updateReasonCancel(String reasonCancelContent) {
this.reasonCancelContent = reasonCancelContent;
return this;
}
/**
* 예약 정보 업데이트
*
* @param updateRequestDto
* @return
*/
public Reserve update(ReserveUpdateRequestDto updateRequestDto) {
this.reserveQty = updateRequestDto.getReserveQty();
this.reservePurposeContent = updateRequestDto.getReservePurposeContent();
this.attachmentCode = updateRequestDto.getAttachmentCode();
this.reserveStartDate = updateRequestDto.getReserveStartDate();
this.reserveEndDate = updateRequestDto.getReserveEndDate();
this.userId = updateRequestDto.getUserId();
this.userEmail = updateRequestDto.getUserEmail();
this.userContactNo = updateRequestDto.getUserContactNo();
return this;
}
/**
* create 정보 세팅
* insert 시 필요
*
* @param createdDate
* @param createdBy
* @return
*/
public Reserve setCreatedInfo(LocalDateTime createdDate, String createdBy) {
this.createdBy = createdBy;
this.createDate = createdDate;
return this;
}
/**
* 예약 수량 양수, 음수 변환
* 예약 취소 시 재고 카운트를 위해
*
* @return
*/
public Reserve conversionReserveQty() {
this.reserveQty = (this.reserveQty * -1);
return this;
}
}

View File

@@ -0,0 +1,208 @@
package org.egovframe.cloud.reservechecksevice.domain.reserve;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.egovframe.cloud.reactive.domain.BaseEntity;
import org.egovframe.cloud.reservechecksevice.domain.location.Location;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reserveitemservice.domain.reserveItem.ReserveItem
*
* 예약 물품 도메인 클래스
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/09
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/09 shinmj 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@ToString
public class ReserveItem extends BaseEntity {
@Id
@Column("reserve_item_id")
private Long reserveItemId; // 예약 물품 id
@Size(max = 200)
@NotNull
@Column("reserve_item_name")
private String reserveItemName; //예약 물품 명
@ToString.Exclude
private Long locationId;
@ToString.Exclude
@Transient
private Location location; //지역
@Size(max = 20)
@NotNull
@Column
private String categoryId; //예약유형 - 공통코드 reserve-category
@Transient
private String categoryName;
@Size(max = 5)
@NotNull
@Column
private Integer totalQty; //총 재고/수용인원 수
@Size(max = 5)
@Column
private Integer inventoryQty; //현재 재고/수용인원 수
@Column
private LocalDateTime operationStartDate; //운영 시작 일
@Column
private LocalDateTime operationEndDate; //운영 종료 일
@Size(max = 20)
@NotNull
@Column
private String reserveMethodId; // 예약 방법 - 공통코드 reserve-method
@Transient
private String reserveMethodName;
@Size(max = 20)
@Column
private String reserveMeansId; // 예약 구분 (인터넷 예약 시) - 공통코드 reserve-means
@Transient
private String reserveMeansName;
@Column
private LocalDateTime requestStartDate; //예약 신청 시작 일시
@Column
private LocalDateTime requestEndDate; //예약 신청 종료 일시
@Column("period_at")
private Boolean isPeriod; //기간 지정 가능 여부 - true: 지정 가능, false: 지정 불가
@Size(max = 3)
@Column
private Integer periodMaxCount; // 최대 예약 가능 일 수
@Size(max = 500)
@Column
private String externalUrl; //외부링크
@Size(max = 20)
@NotNull
@Column
private String selectionMeansId; //선별 방법 - 공통코드 reserve-selection-means
@Transient
private String selectionMeansName;
@Column("paid_at")
private Boolean isPaid; // 유/무료 - false: 무료, true: 유료
@Column
private BigDecimal usageCost; //이용 요금
@Column("use_at")
private Boolean isUse; //사용여부
@Size(max = 4000)
@Column("purpose_content")
private String purpose; //용도
@Size(max = 500)
@Column("item_addr")
private String address; //주소
@Size(max = 20)
@Column
private String targetId; //이용 대상 - 공통코드 reserve-target
@Transient
private String targetName;
@Size(max = 2000)
@Column("excluded_content")
private String excluded; // 사용허가 제외대상
@Size(max = 500)
@Column("homepage_url")
private String homepage; //홈페이지 주소
@Size(max = 50)
@Column("contact_no")
private String contact; //문의처
@Size(max = 200)
@Column("manager_dept_name")
private String managerDept; //담당자 소속
@Size(max = 200)
@Column("manager_name")
private String managerName; //담당자 이름
@Size(max = 50)
@Column("manager_contact_no")
private String managerContact; //담당자 연락처
@Builder
public ReserveItem(Long reserveItemId, String reserveItemName, Long locationId, Location location, String categoryId, String categoryName, Integer totalQty, Integer inventoryQty, LocalDateTime operationStartDate, LocalDateTime operationEndDate, String reserveMethodId, String reserveMethodName, String reserveMeansId, String reserveMeansName, LocalDateTime requestStartDate, LocalDateTime requestEndDate, Boolean isPeriod, Integer periodMaxCount, String externalUrl, String selectionMeansId, String selectionMeansName, Boolean isPaid, BigDecimal usageCost, Boolean isUse, String purpose, String address, String targetId, String targetName, String excluded, String homepage, String contact, String managerDept, String managerName, String managerContact) {
this.reserveItemId = reserveItemId;
this.reserveItemName = reserveItemName;
this.locationId = locationId;
this.location = location;
this.categoryId = categoryId;
this.categoryName = categoryName;
this.totalQty = totalQty;
this.inventoryQty = inventoryQty;
this.operationStartDate = operationStartDate;
this.operationEndDate = operationEndDate;
this.reserveMethodId = reserveMethodId;
this.reserveMethodName = reserveMethodName;
this.reserveMeansId = reserveMeansId;
this.reserveMeansName = reserveMeansName;
this.requestStartDate = requestStartDate;
this.requestEndDate = requestEndDate;
this.isPeriod = isPeriod;
this.periodMaxCount = periodMaxCount;
this.externalUrl = externalUrl;
this.selectionMeansId = selectionMeansId;
this.selectionMeansName = selectionMeansName;
this.isPaid = isPaid;
this.usageCost = usageCost;
this.isUse = isUse;
this.purpose = purpose;
this.address = address;
this.targetId = targetId;
this.targetName = targetName;
this.excluded = excluded;
this.homepage = homepage;
this.contact = contact;
this.managerDept = managerDept;
this.managerName = managerName;
this.managerContact = managerContact;
}
}

View File

@@ -0,0 +1,23 @@
package org.egovframe.cloud.reservechecksevice.domain.reserve;
import org.springframework.data.r2dbc.repository.R2dbcRepository;
/**
* org.egovframe.cloud.reservechecksevice.domain.reserve.ReserveRepository
*
* 예약 도메인 Repository interface
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/15 shinmj 최초 생성
* </pre>
*/
public interface ReserveRepository extends R2dbcRepository<Reserve, String>, ReserveRepositoryCustom {
}

View File

@@ -0,0 +1,44 @@
package org.egovframe.cloud.reservechecksevice.domain.reserve;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.reservechecksevice.api.reserve.dto.ReserveRequestDto;
import org.springframework.data.domain.Pageable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.reservechecksevice.domain.reserve.ReserveRepositoryCustom
*
* 예약 도메인 custom Repository interface
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/15 shinmj 최초 생성
* </pre>
*/
public interface ReserveRepositoryCustom {
Flux<Reserve> search(ReserveRequestDto requestDto, Pageable pageable);
Mono<Long> searchCount(ReserveRequestDto requestDto, Pageable pageable);
Mono<Reserve> findReserveById(String reserveId);
Flux<Reserve> searchForUser(ReserveRequestDto requestDto, Pageable pageable, String userId);
Mono<Long> searchCountForUser(ReserveRequestDto requestDto, Pageable pageable, String userId);
Mono<Reserve> loadRelations(Reserve reserve);
Flux<Reserve> findAllByReserveDate(Long reserveItemId, LocalDateTime startDate, LocalDateTime endDate);
Flux<Reserve> findAllByReserveDateWithoutSelf(String reserveId, Long reserveItemId, LocalDateTime startDate, LocalDateTime endDate);
Mono<Long> findAllByReserveDateWithoutSelfCount(String reserveId, Long reserveItemId, LocalDateTime startDate, LocalDateTime endDate);
Mono<Reserve> insert(Reserve reserve);
}

View File

@@ -0,0 +1,296 @@
package org.egovframe.cloud.reservechecksevice.domain.reserve;
import static org.springframework.data.relational.core.query.Criteria.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.egovframe.cloud.reservechecksevice.api.reserve.dto.ReserveRequestDto;
import org.egovframe.cloud.reservechecksevice.client.ReserveItemServiceClient;
import org.egovframe.cloud.reservechecksevice.client.UserServiceClient;
import org.egovframe.cloud.reservechecksevice.client.dto.UserResponseDto;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.query.Query;
import org.springframework.util.StringUtils;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.reactor.circuitbreaker.operator.CircuitBreakerOperator;
import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* org.egovframe.cloud.reservechecksevice.domain.reserve.ReserveRepositoryImpl
*
* 예약 도메인 custom repository 구현 클래스
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/15 shinmj 최초 생성
* </pre>
*/
@RequiredArgsConstructor
public class ReserveRepositoryImpl implements ReserveRepositoryCustom{
private static final String RESERVE_ITEM_CIRCUIT_BREAKER_NAME = "reserve-item";
private static final String USER_CIRCUIT_BREAKER_NAME = "user";
private final R2dbcEntityTemplate entityTemplate;
private final ReserveItemServiceClient reserveItemServiceClient;
private final UserServiceClient userServiceClient;
private final CircuitBreakerRegistry circuitBreakerRegistry;
/**
* 조회조건 목록 조회
*
* @param requestDto
* @param pageable
* @return
*/
@Override
public Flux<Reserve> search(ReserveRequestDto requestDto, Pageable pageable) {
return entityTemplate.select(Reserve.class)
.matching(Query.query(Criteria.from(whereQuery(requestDto)))
.sort(Sort.by(Sort.Direction.DESC, "create_date"))
.with(pageable))
.all()
.flatMap(this::loadRelations)
.switchIfEmpty(Flux.empty());
}
/**
* 조회조건 목록 조회시 총 count 조회
*
* @param requestDto
* @param pageable
* @return
*/
@Override
public Mono<Long> searchCount(ReserveRequestDto requestDto, Pageable pageable) {
return entityTemplate.select(Reserve.class)
.matching(Query.query(Criteria.from(whereQuery(requestDto)))
.sort(Sort.by(Sort.Direction.DESC, "create_date"))
.with(pageable))
.count();
}
/**
* 예약정보 한건 조회시 relation 같이 조회
*
* @param reserveId
* @return
*/
@Override
public Mono<Reserve> findReserveById(String reserveId) {
return entityTemplate.selectOne(Query.query(where("reserve_id").is(reserveId)), Reserve.class)
.flatMap(this::loadRelations)
.switchIfEmpty(Mono.empty());
}
/**
* 사용자 예약 목록 조회
*
* @param requestDto
* @param pageable
* @param userId
* @return
*/
@Override
public Flux<Reserve> searchForUser(ReserveRequestDto requestDto, Pageable pageable, String userId) {
Criteria where = Criteria.from(whereQuery(requestDto));
return entityTemplate.select(Reserve.class)
.matching(Query.query(where.and(where("user_id").is(userId)))
.sort(Sort.by(Sort.Direction.DESC, "create_date"))
.with(pageable))
.all()
.flatMap(this::loadRelations)
.switchIfEmpty(Flux.empty());
}
/**
* 사용자 예약 목록 건수 조회
*
* @param requestDto
* @param pageable
* @param userId
* @return
*/
@Override
public Mono<Long> searchCountForUser(ReserveRequestDto requestDto, Pageable pageable, String userId) {
Criteria where = Criteria.from(whereQuery(requestDto));
return entityTemplate.select(Reserve.class)
.matching(Query.query(where.and(where("user_id").is(userId)))
.sort(Sort.by(Sort.Direction.DESC, "create_date"))
.with(pageable))
.count();
}
/**
* relation 조회
*
* @param reserve
* @return
*/
@Override
public Mono<Reserve> loadRelations(final Reserve reserve) {
//load user
Mono<Reserve> mono = Mono.just(reserve)
.zipWith(findUserByUserId(reserve.getUserId()))
.map(tuple -> tuple.getT1().setUser(tuple.getT2()))
.switchIfEmpty(Mono.just(reserve));
//load reserveItem
mono = mono.zipWith(findReserveItemWithRelation(reserve.getReserveItemId()))
.map(tuple -> tuple.getT1().setReserveItem(tuple.getT2()))
.switchIfEmpty(Mono.just(reserve));
return mono;
}
/**
* 조회 기간에 예약된 건 조회
*
* @param reserveItemId
* @param startDate
* @param endDate
* @return
*/
@Override
public Flux<Reserve> findAllByReserveDate(Long reserveItemId, LocalDateTime startDate, LocalDateTime endDate) {
return entityTemplate.select(Reserve.class)
.matching(Query.query(where("reserve_item_id").is(reserveItemId)
.and ("reserve_start_date").lessThanOrEquals(endDate)
.and("reserve_end_date").greaterThanOrEquals(startDate)
))
.all();
}
/**
* 조회 기간에 예약된 건 조회
* 현 예약건은 제외
*
* @param reserveItemId
* @param startDate
* @param endDate
* @return
*/
@Override
public Flux<Reserve> findAllByReserveDateWithoutSelf(String reserveId, Long reserveItemId, LocalDateTime startDate, LocalDateTime endDate) {
return entityTemplate.select(Reserve.class)
.matching(Query.query(where("reserve_item_id").is(reserveItemId)
.and ("reserve_start_date").lessThanOrEquals(endDate)
.and("reserve_end_date").greaterThanOrEquals(startDate)
.and("reserve_id").not(reserveId)
))
.all();
}
/**
* 조회 기간에 예약된 건수 조회
* 현 예약건은 제외
*
* @param reserveItemId
* @param startDate
* @param endDate
* @return
*/
@Override
public Mono<Long> findAllByReserveDateWithoutSelfCount(String reserveId, Long reserveItemId, LocalDateTime startDate, LocalDateTime endDate) {
return entityTemplate.select(Reserve.class)
.matching(Query.query(where("reserve_item_id").is(reserveItemId)
.and ("reserve_start_date").lessThanOrEquals(endDate)
.and("reserve_end_date").greaterThanOrEquals(startDate)
.and("reserve_id").not(reserveId)
))
.count();
}
/**
* 예약 insert
* pk(reserveId)를 서비스에서 생성하여 insert 하기 위함.
*
* @param reserve
* @return
*/
@Override
public Mono<Reserve> insert(Reserve reserve) {
return entityTemplate.insert(reserve);
}
/**
* 예약 물품 정보 조회
*
* @param reserveItemId
* @return
*/
private Mono<ReserveItem> findReserveItemWithRelation(Long reserveItemId) {
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(RESERVE_ITEM_CIRCUIT_BREAKER_NAME);
return reserveItemServiceClient.findByIdWithRelations(reserveItemId)
.transform(CircuitBreakerOperator.of(circuitBreaker))
.onErrorResume(throwable -> Mono.empty())
.switchIfEmpty(Mono.empty())
.flatMap(reserveItemRelationResponseDto -> Mono.just(reserveItemRelationResponseDto.toEntity()));
}
/**
* 예약자 정보 조회
*
* @param userId
* @return
*/
private Mono<UserResponseDto> findUserByUserId(String userId ) {
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(USER_CIRCUIT_BREAKER_NAME);
return userServiceClient.findByUserId(userId)
.transform(CircuitBreakerOperator.of(circuitBreaker))
.onErrorResume(throwable -> Mono.empty());
}
/**
* 조회조건 쿼리
*
* @param requestDto
* @return
*/
private List<Criteria> whereQuery(ReserveRequestDto requestDto) {
List<Criteria>criteriaList = new ArrayList<>();
if (requestDto.getLocationId() != null) {
criteriaList.add(where("location_id").is(requestDto.getLocationId()));
}
if (requestDto.getCategoryId() != null) {
criteriaList.add(where("category_id").is(requestDto.getCategoryId()));
}
if (StringUtils.hasText(requestDto.getKeyword())) {
if ("item".equals(requestDto.getKeywordType())) {
criteriaList.add(where("reserve_item_id").like(likeText(requestDto.getKeyword())));
}
}
return criteriaList;
}
/**
* like 검색
*
* @param keyword
* @return
*/
private String likeText(String keyword) {
return "%" + keyword + "%";
}
}

View File

@@ -0,0 +1,37 @@
package org.egovframe.cloud.reservechecksevice.domain.reserve;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* org.egovframe.cloud.reservechecksevice.domain.reserve.ReserveStatus
*
* 예약 상태 enum class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/15 shinmj 최초 생성
* </pre>
*/
@Getter
@RequiredArgsConstructor
public enum ReserveStatus {
REQUEST("request", "예약 신청"),
APPROVE("approve", "예약 승인"),
CANCEL("cancel", "예약 취소"),
DONE("done", "완료");
private final String key;
private final String title;
public boolean isEquals(String status) {
return this.getKey().equals(status);
}
}

View File

@@ -0,0 +1,626 @@
package org.egovframe.cloud.reservechecksevice.service.reserve;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.stream.IntStream;
import org.egovframe.cloud.common.domain.Role;
import org.egovframe.cloud.common.dto.AttachmentEntityMessage;
import org.egovframe.cloud.common.exception.BusinessMessageException;
import org.egovframe.cloud.reactive.service.ReactiveAbstractService;
import org.egovframe.cloud.reservechecksevice.api.reserve.dto.ReserveCancelRequestDto;
import org.egovframe.cloud.reservechecksevice.api.reserve.dto.ReserveListResponseDto;
import org.egovframe.cloud.reservechecksevice.api.reserve.dto.ReserveRequestDto;
import org.egovframe.cloud.reservechecksevice.api.reserve.dto.ReserveResponseDto;
import org.egovframe.cloud.reservechecksevice.api.reserve.dto.ReserveSaveRequestDto;
import org.egovframe.cloud.reservechecksevice.api.reserve.dto.ReserveUpdateRequestDto;
import org.egovframe.cloud.reservechecksevice.client.ReserveItemServiceClient;
import org.egovframe.cloud.reservechecksevice.client.dto.ReserveItemResponseDto;
import org.egovframe.cloud.reservechecksevice.domain.reserve.Category;
import org.egovframe.cloud.reservechecksevice.domain.reserve.Reserve;
import org.egovframe.cloud.reservechecksevice.domain.reserve.ReserveRepository;
import org.egovframe.cloud.reservechecksevice.domain.reserve.ReserveStatus;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.reactor.circuitbreaker.operator.CircuitBreakerOperator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* org.egovframe.cloud.reservechecksevice.service.reserve.ReserveService
*
* 예약 service 클래스
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/15 shinmj 최초 생성
* </pre>
*/
@Slf4j
@RequiredArgsConstructor
@Transactional
@Service
public class ReserveService extends ReactiveAbstractService {
private static final String RESERVE_ITEM_CIRCUIT_BREAKER_NAME = "reserve-item";
private static final String CHECK_RESERVE_MEANS = "realtime";
private final ReserveRepository reserveRepository;
private final ReserveItemServiceClient reserveItemServiceClient;
private final CircuitBreakerRegistry circuitBreakerRegistry;
private final StreamBridge streamBridge;
/**
* entity -> dto 변환
*
* @param reserve
* @return
*/
private Mono<ReserveResponseDto> convertReserveResponseDto(Reserve reserve) {
return Mono.just(ReserveResponseDto.builder()
.entity(reserve)
.build());
}
/**
* entity -> 목록 dto 변환
*
* @param reserve
* @return
*/
private Mono<ReserveListResponseDto> convertReserveListResponseDto(Reserve reserve) {
return Mono.just(ReserveListResponseDto.builder()
.entity(reserve)
.build());
}
/**
* 현재 로그인 사용자가 관리자인지 체크
*
* @return
*/
private Mono<Boolean> getIsAdmin() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getAuthorities)
.map(grantedAuthorities -> {
List<SimpleGrantedAuthority> authorities =
new ArrayList<>((Collection<? extends SimpleGrantedAuthority>) grantedAuthorities);
SimpleGrantedAuthority adminRole = new SimpleGrantedAuthority(Role.ADMIN.getKey());
return authorities.contains(adminRole);
});
}
/**
* 현재 로그인 사용자 id
*
* @return
*/
private Mono<String> getUserId() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(String.class::cast);
}
/**
* 목록 조회
*
* @param requestDto
* @param pageable
* @return
*/
@Transactional(readOnly = true)
public Mono<Page<ReserveListResponseDto>> search(ReserveRequestDto requestDto, Pageable pageable) {
return reserveRepository.search(requestDto, pageable)
.switchIfEmpty(Flux.empty())
.flatMap(this::convertReserveListResponseDto)
.collectList()
.zipWith(reserveRepository.searchCount(requestDto, pageable))
.flatMap(tuple -> Mono.just(new PageImpl<>(tuple.getT1(), pageable, tuple.getT2())));
}
/**
* 한건 조회 dto return
*
* @param reserveId
* @return
*/
@Transactional(readOnly = true)
public Mono<ReserveResponseDto> findReserveById(String reserveId) {
return reserveRepository.findReserveById(reserveId)
.switchIfEmpty(monoResponseStatusEntityNotFoundException(reserveId))
.flatMap(this::convertReserveResponseDto);
}
/**
* 사용자용 예약 목록 조회 (로그인 사용자의 예약정보만 조회)
*
* @param userId
* @param requestDto
* @param pageable
* @return
*/
@Transactional(readOnly = true)
public Mono<Page<ReserveListResponseDto>> searchForUser(String userId, ReserveRequestDto requestDto, Pageable pageable) {
return reserveRepository.searchForUser(requestDto, pageable, userId)
.switchIfEmpty(Flux.empty())
.flatMap(this::convertReserveListResponseDto)
.collectList()
.zipWith(reserveRepository.searchCountForUser(requestDto, pageable, userId))
.flatMap(tuple -> Mono.just(new PageImpl<>(tuple.getT1(), pageable, tuple.getT2())));
}
/**
* 예약 정보 취소
*
* @param reserveId
* @param cancelRequestDto
* @return
*/
public Mono<Void> cancel(String reserveId, ReserveCancelRequestDto cancelRequestDto) {
return getIsAdmin().flatMap(isAdmin -> {
if (isAdmin) {
return reserveCancel(reserveId, cancelRequestDto);
}
return findById(reserveId)
.zipWith(getUserId())
.flatMap(tuple -> {
if (tuple.getT1().getUserId().equals(tuple.getT2())) {
return Mono.just(tuple.getT1());
}
//해당 예약은 취소할 수 없습니다.
return Mono.error(new BusinessMessageException(getMessage("valid.cant_cancel")));
})
.onErrorResume(throwable -> Mono.error(throwable))
.flatMap(reserve -> reserveCancel(reserveId, cancelRequestDto));
});
}
/**
* 예약 상태 취소로 변경
*
* @param reserveId
* @param cancelRequestDto
* @return
*/
private Mono<Void> reserveCancel(String reserveId, ReserveCancelRequestDto cancelRequestDto) {
return findById(reserveId)
.map(reserve -> {
if (ReserveStatus.DONE.isEquals(reserve.getReserveStatusId())) {
//해당 예약은 이미 실행되어 취소할 수 없습니다.
throw new BusinessMessageException(getMessage("valid.cant_cancel_because_done"));
}else {
return reserve.updateStatus(ReserveStatus.CANCEL.getKey())
.updateReasonCancel(cancelRequestDto.getReasonCancelContent());
}
})
.flatMap(reserve -> Mono.just(reserve.conversionReserveQty()))
.flatMap(this::updateInventory)
.onErrorResume(throwable -> Mono.error(throwable))
.flatMap(reserve -> Mono.just(reserve.conversionReserveQty()))
.flatMap(reserveRepository::save)
.then();
}
/**
* 예약 정보 승인
*
* @param reserveId
* @return
*/
public Mono<Void> approve(String reserveId) {
return getIsAdmin()
.flatMap(isAdmin -> {
if (isAdmin) {
return Mono.just(reserveId);
}
//관리자만 승인할 수 있습니다.
return Mono.error(new BusinessMessageException(getMessage("valid.manager_approve")));
})
.onErrorResume(throwable -> Mono.error(throwable))
.flatMap(this::checkApprove)
.onErrorResume(throwable -> Mono.error(throwable))
.flatMap(reserveRepository::save).then();
}
/**
* 승인 전 validate check 및 교육인 경우 재고 업데이트
*
* @param reserveId
* @return
*/
private Mono<Reserve> checkApprove(String reserveId) {
return findById(reserveId)
.flatMap(this::checkReserveItems)
.onErrorResume(throwable -> Mono.error(throwable))
.map(reserve -> reserve.updateStatus(ReserveStatus.APPROVE.getKey()))
.flatMap(this::updateInventory);
}
/**
* 예약 물품 재고 및 예약 일자 체크
*
* @param reserve
* @return
*/
private Mono<Reserve> checkReserveItems(Reserve reserve) {
return reserveItemServiceClient.findById(reserve.getReserveItemId())
.transform(CircuitBreakerOperator.of(circuitBreakerRegistry.circuitBreaker(RESERVE_ITEM_CIRCUIT_BREAKER_NAME)))
.onErrorResume(throwable -> Mono.empty())
.flatMap(reserveItemResponseDto -> {
// validation check
if (Category.SPACE.isEquals(reserveItemResponseDto.getCategoryId())) {
return this.checkSpace(reserveItemResponseDto, reserve);
}else if (Category.EQUIPMENT.isEquals(reserveItemResponseDto.getCategoryId())) {
return this.checkEquipment(reserveItemResponseDto, reserve);
}else if (Category.EDUCATION.isEquals(reserveItemResponseDto.getCategoryId())) {
return this.checkEducation(reserveItemResponseDto, reserve);
}
return Mono.just(reserve);
});
}
/**
* 예약 날짜 validation
*
* @param reserveItem
* @param reserve
* @return
*/
private Mono<Reserve> checkReserveDate(ReserveItemResponseDto reserveItem, Reserve reserve) {
LocalDateTime startDate = reserveItem.getReserveMeansId().equals(CHECK_RESERVE_MEANS) ?
reserveItem.getRequestStartDate() : reserveItem.getOperationStartDate();
LocalDateTime endDate = reserveItem.getReserveMeansId().equals(CHECK_RESERVE_MEANS) ?
reserveItem.getRequestEndDate() : reserveItem.getOperationEndDate();
if (reserve.getReserveStartDate().isBefore(startDate)) {
//{0}이 {1} 보다 빠릅니다. 시작일, 운영/예약 시작일
return Mono.error(new BusinessMessageException(getMessage("valid.to_be_fast.format", new Object[]{getMessage("common.start_date"),
getMessage("reserve_item.operation")+getMessage("reserve")+" "+getMessage("common.start_date")})));
}
if (reserve.getReserveEndDate().isAfter(endDate)) {
//{0}이 {1} 보다 늦습니다. 종료일, 운영/예약 종료일
return Mono.error(new BusinessMessageException(getMessage("valid.to_be_slow.format", new Object[]{getMessage("common.end_date"),
getMessage("reserve_item.operation")+getMessage("reserve")+" "+getMessage("common.end_date")})));
}
if (reserveItem.getIsPeriod()) {
long between = ChronoUnit.DAYS.between(reserve.getReserveStartDate(), reserve.getReserveEndDate());
if (reserveItem.getPeriodMaxCount() < between) {
//최대 예약 가능 일수보다 예약기간이 깁니다. (최대 예약 가능일 수 : {0})
return Mono.error(new BusinessMessageException(getMessage("valid.reserve_period", new Object[]{reserveItem.getPeriodMaxCount()})));
}
}
return Mono.just(reserve);
}
/**
* 공간 예약 시 예약 날짜에 다른 예약이 있는지 체크
*
* @param reserveItem
* @param reserve
* @return
*/
private Mono<Reserve> checkSpace(ReserveItemResponseDto reserveItem, Reserve reserve) {
return this.checkReserveDate(reserveItem, reserve)
.flatMap(isValid -> reserveRepository.findAllByReserveDateWithoutSelfCount(
reserve.getReserveId(),
reserveItem.getReserveItemId(),
reserve.getReserveStartDate(),
reserve.getReserveEndDate())
.flatMap(count -> {
if (count > 0) {
//"해당 날짜에는 예약할 수 없습니다."
return Mono.error(new BusinessMessageException(getMessage("valid.reserve_date")));
}
return Mono.just(reserve);
})
);
}
/**
* 장비 예약 시 예약 날짜에 예약 가능한 재고 체크
*
* @param reserveItem
* @param reserve
* @return
*/
private Mono<Reserve> checkEquipment(ReserveItemResponseDto reserveItem, Reserve reserve) {
return this.checkReserveDate(reserveItem, reserve)
.flatMap(entity -> this.getMaxByReserveDateWithoutSelf(
entity.getReserveId(),
reserveItem.getReserveItemId(),
entity.getReserveStartDate(),
entity.getReserveEndDate())
.flatMap(max -> {
if ((reserveItem.getTotalQty() - max) < reserve.getReserveQty()) {
return Mono.just(false);
}
return Mono.just(true);
})
.flatMap(isValid -> {
if (!isValid) {
//해당 날짜에 예약할 수 있는 재고수량이 없습니다.
return Mono.error(new BusinessMessageException(getMessage("valid.reserve_count")));
}
return Mono.just(reserve);
})
);
}
/**
* 교육 예약 시 재고 체크
*
* @param reserveItem
* @param reserve
* @return
*/
private Mono<Reserve> checkEducation(ReserveItemResponseDto reserveItem, Reserve reserve) {
return Mono.just(reserveItem)
.flatMap(reserveItemResponseDto -> {
LocalDateTime now = LocalDateTime.now();
LocalDateTime startDate = reserveItemResponseDto.getReserveMeansId().equals(CHECK_RESERVE_MEANS) ?
reserveItemResponseDto.getRequestStartDate() : reserveItemResponseDto.getOperationStartDate();
LocalDateTime endDate = reserveItemResponseDto.getReserveMeansId().equals(CHECK_RESERVE_MEANS) ?
reserveItemResponseDto.getRequestEndDate() : reserveItemResponseDto.getOperationEndDate();
if (!(now.isAfter(startDate) && now.isBefore(endDate))) {
//해당 날짜에는 예약할 수 없습니다.
return Mono.error(new BusinessMessageException(getMessage("valid.reserve_date")));
}
if (reserveItemResponseDto.getInventoryQty() <= 0) {
//"예약이 마감되었습니다."
return Mono.error(new BusinessMessageException(getMessage("valid.reserve_close")));
}
if (reserveItemResponseDto.getInventoryQty() < reserve.getReserveQty()) {
//예약가능한 인원이 부족합니다. (남은 인원 : {0})
return Mono.error(new BusinessMessageException(getMessage("valid.reserve_number_of_people", new Object[]{reserveItemResponseDto.getInventoryQty()})));
}
return Mono.just(reserve);
});
}
/**
* 예약 정보 수정
*
* @param reserveId
* @return
*/
public Mono<Reserve> update(String reserveId, ReserveUpdateRequestDto updateRequestDto) {
return getIsAdmin().flatMap(isAdmin -> {
if (isAdmin) {
return updateReserve(reserveId, updateRequestDto);
}
return updateReserveForUser(reserveId, updateRequestDto);
});
}
/**
* 사용자 예약 수정
*
* @param reserveId
* @param updateRequestDto
* @return
*/
private Mono<Reserve> updateReserveForUser(String reserveId, ReserveUpdateRequestDto updateRequestDto) {
return findById(reserveId)
.zipWith(getUserId())
.map(tuple -> {
if (!tuple.getT1().getUserId().equals(tuple.getT2())) {
//"해당 예약은 수정할 수 없습니다."
throw new BusinessMessageException(getMessage("valid.reserve_not_update"));
}
if (!ReserveStatus.REQUEST.getKey().equals(tuple.getT1().getReserveStatusId())) {
//예약 신청 상태인 경우에만 수정 가능합니다.
throw new BusinessMessageException(getMessage("valid.reserve_not_update_status"));
}
return tuple.getT1().update(updateRequestDto);
})
.flatMap(this::checkReserveItems)
.onErrorResume(throwable -> Mono.error(throwable))
.flatMap(this::updateInventory)
.onErrorResume(throwable -> Mono.error(throwable))
.flatMap(reserveRepository::save);
}
/**
* 관리자 예약 수정
*
* @param reserveId
* @param updateRequestDto
* @return
*/
private Mono<Reserve> updateReserve(String reserveId, ReserveUpdateRequestDto updateRequestDto) {
return findById(reserveId)
.map(reserve -> {
if (!ReserveStatus.REQUEST.getKey().equals(reserve.getReserveStatusId())) {
//예약 신청 상태인 경우에만 수정 가능합니다.
throw new BusinessMessageException(getMessage("valid.reserve_not_update_status"));
}
return reserve.update(updateRequestDto);
})
.flatMap(this::checkReserveItems)
.onErrorResume(throwable -> Mono.error(throwable))
.flatMap(this::updateInventory)
.onErrorResume(throwable -> Mono.error(throwable))
.flatMap(reserveRepository::save);
}
/**
* 한건 정보 조회 entity return
*
* @param reserveId
* @return
*/
private Mono<Reserve> findById(String reserveId) {
return reserveRepository.findById(reserveId)
.switchIfEmpty(monoResponseStatusEntityNotFoundException(reserveId));
}
/**
* 관리자 예약 신청
* 관리자의 경우 실시간이어도 이벤트 스트림 거치지 않고 바로 예약 처리
*
* @param saveRequestDto
* @return
*/
public Mono<ReserveResponseDto> create(ReserveSaveRequestDto saveRequestDto) {
return Mono.just(saveRequestDto)
.map(dto -> {
String uuid = UUID.randomUUID().toString();
dto.setReserveId(uuid);
return dto.toEntity();
})
.zipWith(getUserId())
.flatMap(tuple -> Mono.just(tuple.getT1().setCreatedInfo(LocalDateTime.now(), tuple.getT2())))
.flatMap(this::checkReserveItems)
.onErrorResume(throwable -> Mono.error(throwable))
.flatMap(this::updateInventory)
.onErrorResume(throwable -> Mono.error(throwable))
.flatMap(reserveRepository::insert)
.flatMap(reserveRepository::loadRelations)
.doOnNext(reserve -> sendAttachmentEntityInfo(streamBridge,
AttachmentEntityMessage.builder()
.attachmentCode(reserve.getAttachmentCode())
.entityName(reserve.getClass().getName())
.entityId(reserve.getReserveId())
.build()))
.flatMap(this::convertReserveResponseDto);
}
/**
* 예약 정보 저장 시 재고 변경
*
* @param reserve
* @return
*/
private Mono<Reserve> updateInventory(Reserve reserve) {
return Mono.just(reserve)
.flatMap(reserve1 -> {
if (!Category.EDUCATION.isEquals(reserve1.getCategoryId())) {
return Mono.just(reserve1);
}
// return reserveItemServiceClient.updateInventory(reserve.getReserveItemId(), reserve.getReserveQty())
// .transform(CircuitBreakerOperator.of(circuitBreakerRegistry.circuitBreaker(RESERVE_ITEM_CIRCUIT_BREAKER_NAME)))
// .onErrorResume(throwable -> Mono.just(false))
// .flatMap(isSuccess -> {
// if (isSuccess) {
// return Mono.just(reserve);
// }
// //재고 업데이트에 실패했습니다.
// return Mono.error(new BusinessMessageException(getMessage("msg.inventory_failed")));
// });
return null;
});
}
/**
* 예약 물품별 기간안에 있는 예약된 수량 max 조회
*
* @param reserveItemId
* @param startDate
* @param endDate
* @return
*/
public Mono<Integer> countInventory(Long reserveItemId, LocalDateTime startDate, LocalDateTime endDate) {
return reserveItemServiceClient.findById(reserveItemId)
.transform(CircuitBreakerOperator.of(circuitBreakerRegistry.circuitBreaker(RESERVE_ITEM_CIRCUIT_BREAKER_NAME)))
.onErrorResume(throwable -> Mono.empty())
.zipWith(getMaxByReserveDate(reserveItemId, startDate, endDate))
.flatMap(tuple -> Mono.just(tuple.getT1().getTotalQty() - tuple.getT2()));
}
/**
* 예약물품에 대해 날짜별 예약된 수량 max 조회
* 현 예약 건 제외
*
* @param reserveItemId
* @param startDate
* @param endDate
* @return
*/
private Mono<Integer> getMaxByReserveDateWithoutSelf(String reserveId, Long reserveItemId, LocalDateTime startDate, LocalDateTime endDate) {
Flux<Reserve> reserveFlux = reserveRepository.findAllByReserveDateWithoutSelf(reserveId, reserveItemId, startDate, endDate)
.switchIfEmpty(Flux.empty());
return countMax(reserveFlux, startDate, endDate);
}
/**
* 예약물품에 대해 날짜별 예약된 수량 max 조회
*
* @param reserveItemId
* @param startDate
* @param endDate
* @return
*/
private Mono<Integer> getMaxByReserveDate(Long reserveItemId, LocalDateTime startDate, LocalDateTime endDate) {
Flux<Reserve> reserveFlux = reserveRepository.findAllByReserveDate(reserveItemId, startDate, endDate)
.switchIfEmpty(Flux.empty());
return countMax(reserveFlux, startDate, endDate);
}
/**
* get max
*
* @param reserveFlux
* @param startDate
* @param endDate
* @return
*/
private Mono<Integer> countMax(Flux<Reserve> reserveFlux, LocalDateTime startDate, LocalDateTime endDate) {
if (reserveFlux.equals(Flux.empty())) {
return Mono.just(0);
}
long between = ChronoUnit.DAYS.between(startDate, endDate);
return Flux.fromStream(IntStream.iterate(0, i -> i + 1)
.limit(between)
.mapToObj(i -> startDate.plusDays(i)))
.flatMap(localDateTime ->
reserveFlux.map(findReserve -> {
if (localDateTime.isAfter(findReserve.getReserveStartDate())
|| localDateTime.isBefore(findReserve.getReserveEndDate())) {
return findReserve.getReserveQty();
}
return 0;
}).reduce(0, (x1, x2) -> x1 + x2))
.groupBy(integer -> integer)
.flatMap(group -> group.reduce((x1,x2) -> x1 > x2?x1:x2))
.last();
}
}

View File

@@ -0,0 +1,176 @@
package org.egovframe.cloud.reservechecksevice.validator;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import javax.annotation.Resource;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.egovframe.cloud.common.util.MessageUtil;
import org.egovframe.cloud.reservechecksevice.validator.annotation.ReserveSaveValid;
import org.springframework.util.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
/**
* org.egovframe.cloud.reservechecksevice.validator.ReserveSaveValidator
*
* 예약 신청 시 validation check를 하기 위한 custom validator
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/23
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/23 shinmj 최초 생성
* </pre>
*/
@Slf4j
@RequiredArgsConstructor
public class ReserveSaveValidator implements ConstraintValidator<ReserveSaveValid, Object> {
@Resource(
name = "messageUtil"
)
protected MessageUtil messageUtil;
private String message;
@Override
public void initialize(ReserveSaveValid constraintAnnotation) {
message = constraintAnnotation.message();
}
/**
* 예약 신청 시 비지니스 로직에 의한 validation check
*
* @param value
* @param context
* @return
*/
@SneakyThrows
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
boolean fieldValid = true;
String categoryId = String.valueOf(getFieldValue(value, "categoryId"));
if ("education".equals(categoryId)) {
//교육인 경우
//신청인원
fieldValid = checkReserveQty(value, context);
}else if ("equipment".equals(categoryId)) {
//장비인 경우
//신청일자(기간), 신청수량
fieldValid = checkReserveDate(value, context);
fieldValid = checkReserveQty(value, context);
}else if ("place".equals(categoryId)) {
//공간인 경우
//신청일자(기간)
fieldValid = checkReserveDate(value, context);
}
return fieldValid;
}
/**
* 예약 수량 체크
*
* @param value
* @param context
* @return
*/
@SneakyThrows
private boolean checkReserveQty(Object value, ConstraintValidatorContext context) {
if (isNull(value, "reserveQty")) {
context.disableDefaultConstraintViolation();
//예약 수량 값은 필수 입니다.
context.buildConstraintViolationWithTemplate(messageUtil.getMessage("reserve")+" "+messageUtil.getMessage("reserve.count") + messageUtil.getMessage("valid.required"))
.addPropertyNode("reserveQty")
.addConstraintViolation();
return false;
}
return true;
}
/**
* 예약 신청 기간 체크
*
* @param value
* @param context
* @return
*/
@SneakyThrows
private boolean checkReserveDate(Object value, ConstraintValidatorContext context) {
// 예약 신청 기간 필수
if (isNull(value, "reserveStartDate")) {
context.disableDefaultConstraintViolation();
// 예약 신청 시작일 값은 필수 입니다.
context.buildConstraintViolationWithTemplate(messageUtil.getMessage("reserve_item.request")+" "+messageUtil.getMessage("common.start_datetime") + messageUtil.getMessage("valid.required"))
.addPropertyNode("reserveStartDate")
.addConstraintViolation();
return false;
} else if (isNull(value, "reserveEndDate")) {
context.disableDefaultConstraintViolation();
// 예약 신청 종료일 값은 필수 입니다.
context.buildConstraintViolationWithTemplate(messageUtil.getMessage("reserve_item.request")+" "+messageUtil.getMessage("common.end_datetime") + messageUtil.getMessage("valid.required"))
.addPropertyNode("reserveEndDate")
.addConstraintViolation();
return false;
}else {
// 예약 시작일, 종료일 체크
LocalDateTime reserveStartDate = (LocalDateTime) getFieldValue(value, "reserveStartDate");
LocalDateTime reserveEndDate = (LocalDateTime) getFieldValue(value, "reserveEndDate");
if (reserveStartDate.isAfter(reserveEndDate)) {
context.disableDefaultConstraintViolation();
//시작일, 종료일, {0}이 {1}보다 늦습니다.
context.buildConstraintViolationWithTemplate(messageUtil.getMessage("valid.to_be_slow.format", new Object[]{messageUtil.getMessage("common.start_date"), messageUtil.getMessage("common.end_date")}))
.addPropertyNode("reserveStartDate")
.addConstraintViolation();
return false;
}
}
return true;
}
/**
* 해당하는 field의 값 조회
*
* @param object
* @param fieldName
* @return
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
private Object getFieldValue(Object object, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Class<?> clazz = object.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(object);
}
/**
* 해당하는 Field가 null인지 체크
*
* @param object
* @param fieldName
* @return
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
private boolean isNull(Object object, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Class<?> clazz = object.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(object) == null || !StringUtils.hasLength(String.valueOf(field.get(object)));
}
}

View File

@@ -0,0 +1,37 @@
package org.egovframe.cloud.reservechecksevice.validator.annotation;
import org.egovframe.cloud.reservechecksevice.validator.ReserveSaveValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* org.egovframe.cloud.reservechecksevice.validator.annotation.ReserveSaveValid
*
* 예약 신청 시 validation check를 하기 위한 custom annotation
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/09/23
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/23 shinmj 최초 생성
* </pre>
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ReserveSaveValidator.class)
public @interface ReserveSaveValid {
String message() default "저장할 수 없습니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@@ -0,0 +1,14 @@
spring:
application:
name: reserve-check-service
server:
port: 0
# config server actuator
management:
endpoints:
web:
exposure:
include: refresh, health, beans

View File

@@ -0,0 +1,5 @@
spring:
cloud:
config:
uri: http://localhost:8888
name: reserve-check-service

View File

@@ -0,0 +1,26 @@
-- reserve Table Create SQL
CREATE TABLE IF NOT EXISTS reserve
(
`reserve_id` VARCHAR(255) NOT NULL COMMENT '예약 id',
`reserve_item_id` BIGINT NULL COMMENT '예약 물품 id',
`location_id` BIGINT NULL COMMENT '예약 물품-지역 id',
`category_id` VARCHAR(255) NULL COMMENT '예약 물품-유형 id',
`reserve_qty` BIGINT(18) NULL COMMENT '예약 신청인원/수량',
`reserve_purpose_content` VARCHAR(4000) NULL COMMENT '예약신청 목적',
`attachment_code` VARCHAR(255) NULL COMMENT '첨부파일 코드',
`reserve_start_date` DATETIME NULL COMMENT '예약 신청 시작일',
`reserve_end_date` DATETIME NULL COMMENT '예약 신청 종료일',
`reserve_status_id` VARCHAR(20) NULL COMMENT '예약상태 - 공통코드(reserve-status)',
`reason_cancel_content` VARCHAR(4000) NULL COMMENT '예약 취소 사유',
`user_id` VARCHAR(255) NULL COMMENT '예약자 id',
`user_contact_no` VARCHAR(50) NULL COMMENT '예약자 연락처',
`user_email_addr` VARCHAR(500) NULL COMMENT '예약자 이메일',
`create_date` DATETIME NULL COMMENT '생성일',
`created_by` VARCHAR(255) NULL COMMENT '생성자',
`modified_date` DATETIME NULL COMMENT '수정일',
`last_modified_by` VARCHAR(255) NULL COMMENT '수정자',
PRIMARY KEY (reserve_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE reserve COMMENT '예약 신청&확인';

View File

@@ -0,0 +1,13 @@
package org.egovframe.cloud.reservechecksevice;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ReserveCheckSeviceApplicationTests {
@Test
void contextLoads() {
}
}

View File

@@ -0,0 +1,17 @@
package org.egovframe.cloud.reservechecksevice.api;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@EnableConfigurationProperties
@TestPropertySource(properties = {"spring.config.location=classpath:application-test.yml"})
@ActiveProfiles(profiles = "test")
class ReserveApiControllerTest {
}

View File

@@ -0,0 +1,39 @@
package org.egovframe.cloud.reservechecksevice.config;
import io.r2dbc.h2.H2ConnectionConfiguration;
import io.r2dbc.h2.H2ConnectionFactory;
import io.r2dbc.h2.H2ConnectionOption;
import io.r2dbc.spi.ConnectionFactory;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
import org.springframework.r2dbc.connection.init.CompositeDatabasePopulator;
import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer;
import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator;
@Profile("test")
@TestConfiguration
@EnableR2dbcRepositories
public class R2dbcConfig {
@Bean
public H2ConnectionFactory connectionFactory() {
return new H2ConnectionFactory(H2ConnectionConfiguration.builder()
.tcp("localhost", "~/querydsl")
.property(H2ConnectionOption.DB_CLOSE_DELAY, "-1")
.username("sa")
.build());
}
@Bean
public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
initializer.setConnectionFactory(connectionFactory);
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("schema-h2.sql")));
initializer.setDatabasePopulator(populator);
return initializer;
}
}

View File

@@ -0,0 +1,87 @@
package org.egovframe.cloud.reservechecksevice.util;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import java.util.Collections;
import java.util.List;
/**
* org.egovframe.cloud.boardservice.util.RestResponsePage
* <p>
* 페이지 API 조회 시 JSON 형식의 응답 데이터를 페이지 객체를 구현하여 마이그레이션 해주는 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/08
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/08 jooho 최초 생성
* </pre>
*/
public class RestResponsePage<T> extends PageImpl<T> {
/**
* Rest 응답 페이지 생성자
*
* @param content 목록
* @param number 페이지 번호
* @param size 조회할 데이터 수
* @param totalElements 총 데이터 수
* @param pageable 페이지 정보
* @param last 마지막
* @param totalPages 총 페이지
* @param sort 정렬
* @param first 처음
* @param numberOfElements 조회된 데이터 수
*/
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public RestResponsePage(@JsonProperty("content") List<T> content,
@JsonProperty("number") int number,
@JsonProperty("size") int size,
@JsonProperty("totalElements") Long totalElements,
@JsonProperty("pageable") JsonNode pageable,
@JsonProperty("last") boolean last,
@JsonProperty("totalPages") int totalPages,
@JsonProperty("sort") JsonNode sort,
@JsonProperty("first") boolean first,
@JsonProperty("numberOfElements") int numberOfElements) {
super(content, PageRequest.of(number, size), totalElements);
}
/**
* Rest 응답 페이지 생성자
*
* @param content 목록
* @param pageable 페이지 정보
* @param total 총 데이터 수
*/
public RestResponsePage(List<T> content, Pageable pageable, long total) {
super(content, pageable, total);
}
/**
* Rest 응답 페이지 생성자
*
* @param content 목록
*/
public RestResponsePage(List<T> content) {
super(content);
}
/**
* Rest 응답 페이지 생성자
*/
public RestResponsePage() {
super(Collections.emptyList());
}
}

View File

@@ -0,0 +1,16 @@
package org.egovframe.cloud.reservechecksevice.util;
import org.egovframe.cloud.common.domain.Role;
import org.springframework.security.test.context.support.WithSecurityContext;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithCustomMockUser {
String userId() default "user";
Role role() default Role.ADMIN;
}

View File

@@ -0,0 +1,25 @@
package org.egovframe.cloud.reservechecksevice.util;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.test.context.support.WithSecurityContextFactory;
import java.util.ArrayList;
import java.util.List;
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithCustomMockUser> {
@Override
public SecurityContext createSecurityContext(WithCustomMockUser mockUser) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
List<SimpleGrantedAuthority> roleList = new ArrayList<>();
roleList.add(new SimpleGrantedAuthority(mockUser.role().getKey()));
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(mockUser.userId(), null, roleList);
context.setAuthentication(authenticationToken);
return context;
}
}

View File

@@ -0,0 +1,44 @@
spring:
application:
name: reserve-check-service
datasource:
url: jdbc:h2:mem:testdb;MODE=MYSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false
username: sa
password:
driver-class-name: org.h2.Driver
jpa:
hibernate:
generate-ddl: true
ddl-auto: create-drop
properties:
hibernate:
format_sql: true
default_batch_fetch_size: 1000
show-sql: true
h2:
console:
enabled: true
path: /h2
logging.level:
org.hibernate.SQL: debug
file:
directory: ${user.home}/msa-attach-volume
messages:
directory: ${file.directory}/messages
# jwt token
token:
secret: egovframe_user_token
# ftp server
ftp:
enabled: false # ftp 사용 여부, FTP 서버에 최상위 디렉토리 자동 생성 및 구현체를 결정하게 된다.
# eureka 가 포함되면 eureka server 도 등록되므로 해제한다.
eureka:
client:
register-with-eureka: false
fetch-registry: false

View File

@@ -0,0 +1,5 @@
spring:
cloud:
config:
uri: http://localhost:8888
name: reserve-check-service

View File

@@ -0,0 +1,81 @@
-- location Table Create SQL
CREATE TABLE IF NOT EXISTS location
(
location_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '지역 id',
location_name VARCHAR(200) NULL COMMENT '지역 이름',
sort_seq SMALLINT(3) NULL COMMENT '정렬 순서',
use_at TINYINT(1) NULL DEFAULT 1 COMMENT '사용 여부',
created_by VARCHAR(255) NULL COMMENT '생성자',
create_date DATETIME NULL COMMENT '생성일',
last_modified_by VARCHAR(255) NULL COMMENT '수정자',
modified_date DATETIME NULL COMMENT '수정일',
PRIMARY KEY (location_id)
) ;
-- reserve_item Table Create SQL
CREATE TABLE IF NOT EXISTS reserve_item
(
reserve_item_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '예약 물품 id',
reserve_item_name VARCHAR(200) NULL COMMENT '예약 물품 이름',
location_id BIGINT NULL COMMENT '지역 id',
category_id VARCHAR(20) NULL COMMENT '예약유형 - 공통코드 reserve-category',
total_qty BIGINT(18) NULL COMMENT '총 재고/수용인원 수',
inventory_qty BIGINT(18) NULL COMMENT '현재 남은 재고/수용인원 수',
operation_start_date DATETIME NULL COMMENT '운영 시작 일',
operation_end_date DATETIME NULL COMMENT '운영 종료 일',
reserve_method_id VARCHAR(20) NULL COMMENT '예약 방법 - 공통코드 reserve-method',
reserve_means_id VARCHAR(20) NULL COMMENT '예약 구분 (인터넷 예약 시) - 공통코드 reserve-means',
request_start_date DATETIME NULL COMMENT '예약 신청 시작 일시',
request_end_date DATETIME NULL COMMENT '예약 신청 종료 일시',
period_at TINYINT(1) NULL DEFAULT 0 COMMENT '기간 지정 가능 여부 - true: 지정 가능, false: 지정 불가',
period_max_count SMALLINT(3) NULL COMMENT '최대 예약 가능 일 수',
external_url VARCHAR(500) NULL COMMENT '외부링크',
selection_means_id VARCHAR(20) NULL COMMENT '선별 방법 - 공통코드 reserve-selection-means',
free_at TINYINT(1) NULL DEFAULT 1 COMMENT '유/무료 - true: 무료, false: 유료',
usage_cost DECIMAL(18, 0) NULL COMMENT '이용 요금',
use_at TINYINT(1) NULL DEFAULT 1 COMMENT '사용 여부',
purpose_content VARCHAR(4000) NULL COMMENT '용도',
item_addr VARCHAR(500) NULL COMMENT '주소',
target_id VARCHAR(20) NULL COMMENT '이용 대상 - 공통코드 reserve-target',
excluded_content VARCHAR(2000) NULL COMMENT '사용허가 제외대상',
homepage_url VARCHAR(500) NULL COMMENT '홈페이지 url',
contact_no VARCHAR(50) NULL COMMENT '문의처',
manager_dept_name VARCHAR(200) NULL COMMENT '담당자 소속',
manager_name VARCHAR(200) NULL COMMENT '담당자 이름',
manager_contact_no VARCHAR(50) NULL COMMENT '담당자 연락처',
create_date DATETIME NULL COMMENT '생성일',
created_by VARCHAR(255) NULL COMMENT '생성자',
modified_date DATETIME NULL COMMENT '수정일',
last_modified_by VARCHAR(255) NULL COMMENT '수정자',
PRIMARY KEY (reserve_item_id),
CONSTRAINT FK_reserve_item_location_id FOREIGN KEY (location_id)
REFERENCES location (location_id) ON DELETE RESTRICT ON UPDATE RESTRICT
) ;
-- reserve Table Create SQL
CREATE TABLE IF NOT EXISTS reserve
(
reserve_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '예약 id',
reserve_item_id BIGINT NULL COMMENT '예약 물품 id',
reserve_qty BIGINT(18) NULL COMMENT '예약 신청인원/수량',
reserve_purpose_content VARCHAR(4000) NULL COMMENT '예약신청 목적',
attachment_code VARCHAR(255) NULL COMMENT '첨부파일 코드',
reserve_start_date DATETIME NULL COMMENT '예약 신청 시작일',
reserve_end_date DATETIME NULL COMMENT '예약 신청 종료일',
reserve_status_id VARCHAR(20) NULL COMMENT '예약상태 - 공통코드(reserve-status)',
user_id VARCHAR(255) NULL COMMENT '예약자 id',
user_contact_no VARCHAR(50) NULL COMMENT '예약자 연락처',
user_email_addr VARCHAR(500) NULL COMMENT '예약자 이메일',
create_date DATETIME NULL COMMENT '생성일',
created_by VARCHAR(255) NULL COMMENT '생성자',
modified_date DATETIME NULL COMMENT '수정일',
last_modified_by VARCHAR(255) NULL COMMENT '수정자',
PRIMARY KEY (reserve_id),
CONSTRAINT FK_reserve_reserve_item_id FOREIGN KEY (reserve_item_id)
REFERENCES reserve_item (reserve_item_id) ON DELETE RESTRICT ON UPDATE RESTRICT
) ;