일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- securityconfig
- ㅂ
- WebConfigurerAdapter
- 스프링 #스프링 시큐리티 #spring security
- WebSecurity
- 스프링시큐리티
- ㅇㅇㅇㄴㅇ
- Spring Security
- Session1이 그 모든 클라이언트의 저올
- HttpSecurity
- Today
- Total
다오의 개발일지
[Spaceplorer.com] Entity와 기능 개발 - 2 본문
https://dao-blog.tistory.com/93
[Spaceplorer.com] Entity와 기능 개발 - 1
2024.03.04 - [프로젝트] - [Spaceplorer.com] 웹 프로젝트 개요 3/4- [Spaceplorer.com] 웹 프로젝트 아이디어 및 기획 3/4~ 글 요약 이름 : Spaceplorer 개요 : 여러 행성 간 여행을 도와주는 패키지여행안내 및 판매
dao-blog.tistory.com
옵션 엔티티
옵션 엔티티를 추상클래스로 만들고, 이를 상속받는다(상속전략)
Spaceship, Landmark, Hotel, Entertainment는 생성할 때, optionName과 cost, OptionType을 추가로 입력 해, DB에 저장한다.
옵션 엔티티
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Getter
@NoArgsConstructor
@Table(name = "option")
public abstract class Option {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//옵션 명
private String optionName;
//옵션 가격
private Long cost;
//옵션 타입
@Enumerated(EnumType.STRING)
private OptionType optionType;
public Option(String optionName, Long cost, OptionType optionType) {
this.optionName = optionName;
this.cost = cost;
this.optionType = optionType;
}
}
내가 생각한 구성은 프론트엔드에서 여러 옵션들을 선택하고 결제하기를 누르면, 결제페이지로 이동하며
서버는 옵션 id 리스트와 총 계산 금액을 파라미터로 받는다.
@Getter
public class ReceiptRequestDto {
// 총 구매금액(서버에서 검증이 필요)
public Long totalPrice;
//옵셔 id 리스트
public List<Long> optionIdList;
}
여기서 totalPrice를 조작할 가능성이 있기 때문에 바로 결제API로 넘기는 것이 아니라, 직접 DB에 있는
옵션들의 가격들을 더해서 비교를 한다. totalPrice가 같다면 선택된 옵션들을 DB에 저장한다.
이를 위한 선택된 옵션 엔티티도 만들었다.
선택된 옵션 엔티티
@Entity
@Getter
@NoArgsConstructor
public class SelectedOption {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String optionName;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "receipt_id")
private Receipt receipt;
@ManyToOne
@JoinColumn(name = "option_id")
private Option option;
public SelectedOption(Receipt receipt, Option option) {
this.receipt = receipt;
this.option = option;
this.optionName = option.getOptionName();
}
public void setReceipt(Receipt receipt) {
this.receipt = receipt;
}
}
SelectedOption 같은 경우, 직접 호출하지 않고 Receipt에 양방향으로 연결되어 List로써 출력하게끔 만들었다.
혹시나 SelectedOption을 사용하게 될 경우, Receipt는 사용할 일이 없기에 Lazy loading으로 두었다.
Receipt 엔티티
@Entity
@Getter
@NoArgsConstructor
@Table(name = "receipt")
public class Receipt {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long totalPrice;
//선택된 옵션 리스트 eager
@OneToMany(mappedBy = "receipt")
@Column(nullable = false)
private final List<SelectedOption> selectedOptionList = new ArrayList<>();
public Receipt(Long totalPrice) {
this.totalPrice = totalPrice;
}
public void addSelectedOptionList(SelectedOption selectedOption){
selectedOptionList.add(selectedOption);
selectedOption.setReceipt(this);
}
}
아직 구체화되진 않아서 선택된 옵션들, 총 가격만 넣어놓았다. 프론트엔드를 개발하면서 필요한 필드들이 있다면 계속 추가할 생각이다.
ReceiptService.createReceipt
@Transactional
public ResponseEntity<ApiResponseDto<ReceiptResponseDto>> createReceipt(ReceiptRequestDto receiptRequestDto) {
List<Option> selectedOptionEntityList = optionRepository.findByIdIn(receiptRequestDto.getOptionIdList());
if(selectedOptionEntityList.size() != receiptRequestDto.getOptionIdList().size())
{
log.error("[Does not match id list :{} != {}]", selectedOptionEntityList.size(), receiptRequestDto.getOptionIdList().size());
return util.responseGenerator(BAD_REQUEST, null, NOT_MATCH_OPTION, BAD_REQUEST.value());
}
...
Receipt를 생성하는 메서드이다.
요청파라미터에 담아온 옵션 리스트와, DB에 저장되어있는 옵션 리스트의 개수를 비교한다.
많은 동작을 진행하므로 하나의 트랙잭션 단위로 묶기위해 @Transactional 어노테이션을 사용했다.
Long calculatedTotalPrice = calculateTotalPrice(selectedOptionEntityList);
log.debug("[CalculatedTotalPrice:{}]",calculatedTotalPrice);
...
}
private Long calculateTotalPrice(List<Option> selectedOptionEntityList) {
Long totalPrice = 0L;
for (Option option : selectedOptionEntityList) {
totalPrice += option.getCost();
}
return totalPrice;
}
총 금액을 계산한다.
//총 계산금액 비교
if(!calculatedTotalPrice.equals(receiptRequestDto.getTotalPrice())){
log.error("[Does not match total price :{} != {}]", receiptRequestDto.getTotalPrice(), calculatedTotalPrice);
return util.responseGenerator(BAD_REQUEST, null, NOT_MATCH_TOTAL_PRICE, BAD_REQUEST.value());
}
//영수증 저장
Receipt createdReceipt = receiptRepository.save(new Receipt(calculatedTotalPrice));
Receipt entity = createSelectedOptionEntity(selectedOptionRepository, selectedOptionEntityList, createdReceipt);
log.info("[Created receipt:{}]",entity);
return util.generateDtoResponse(log, CREATED, Optional.of(entity), ReceiptResponseDto.class, CREATED_RECEIPT);
- 요청파라미터의 총 금액과 계산한 총 금액을 비교한다.
- 영수증을 저장한다. 그리고 선택된 옵션 리스트에 옵션들을 저장한다.
.getReceiptById
public ResponseEntity<ApiResponseDto<ReceiptResponseDto>> getReceiptById(Long id) {
Optional<Receipt> entity = receiptRepository.findById(id);
return util.generateDtoResponse(log, OK, entity, ReceiptResponseDto.class, FOUND_RECEIPT);
}
영수증 아이디로 조회한다.
이 메서드는 두 군데 사용할 예정이다.
- 결제가 완료된 후 결제내역창으로 리다이렉트 시켜서 보여준다. 이때 id는 로컬스토리지로 저장해 서버로 넘기게 한다.
- 두번째는, 마이페이지같은 곳에서 결제내역 상세페이지로 이동하는 경우이다.
Board
여행을 다녀온 후기나 관련 커뮤니티 기능을 만들고 싶었다. 카테고리를 각 행성별로, 도시는 태그로해서 구분짓게 하였다.
/**
* 게시판은 인터렉티브할 필요가 없기때문에, ThymeLeaf를 사용하여 SSR로 개발해보기
* R : 모든 방문자 가능
* CUD : 로그인 한 유저만 사능
*/
@Controller
@RequiredArgsConstructor
@RequestMapping("/boards")
public class BoardController {
private final BoardService boardService;
//전체 게시판 리스트를 출력
@GetMapping()
public String getBoardList(Model model){
model.addAttribute("boardList",boardService.getBoardList());
return "board_list";
}
//게시글 1개 조회, 게시글 클릭 시 상세 게시글
@GetMapping("/{board_id}")
public String getBoardById(
@PathVariable("board_id") Long id,
Model model){
model.addAttribute("board",boardService.getBoardById(id));
return "board_detail";
}
//게시글 등록
@PostMapping
public String createBoard(@RequestBody BoardRequestDto boardRequestDto){
BoardResponseDto boardResponseDto = boardService.createBoard(boardRequestDto);
return "redirect:/boards/"+boardResponseDto.getId();
}
//게시글 삭제
@DeleteMapping("/{board_id}")
public String deleteBoardById(@PathVariable("board_id") Long id){
boardService.deleteBoardById(id);
return "redirect:/boards";
}
패키지 소개부터 결제창까지는 애니메이션 효과나 SPA처럼 사용하고 싶었기 떄문에, CSR을 위해 데이터를 전달했다면,
게시판은 인터렉티브한 것보다 비교적 정적인 페이지인 것 같아. 템플릿엔진 연습도 할겸 SSR로 개발하기로 했다.
기존의 API방식인 경우, ResponseEntity에 응답코드, 데이터, 응답메시지등을 담아서 보냈다면, 여기는 GlobalExceptionHandler를 만들어서, 예외가 발생했을 시, 에러페이지로 이동, 메시지를 alert으로 띄우게끔 만들었다.
GlobalExceptionHandler
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(value = ResponseStatusException.class)
public String handleIllegalArgumentException(ResponseStatusException e, Model model) {
log.debug("[exception occurred:{}]",e.getReason());
model.addAttribute("responseStatusException", e);
return "errorPage"; // 에러 페이지로의 뷰 이름
}
}
ResponseStatusException이 발생하면 여기서 catch해서 model로 메시지를 넣고, errorPage로 리다이렉트 시킨다.
게시글의 조회 중, 조회 수 증가에 대해서 이를 비동기 처리하면 속도가 빨라지지 않을까 생각했다.
@Configuration
//비동기 처리용
@EnableAsync
public class BeanConfig {
...
}
이를 위한 Config 클래스에 @EnableAsync 어노테이션을 붙이고, 비동기 처리하려는 메서드에 @Async 붙히면 간단하게 비동기 처리가 가능하다.
@Async
public void incrementViewsAsync(Board board) {
board.incrementViews();
boardRepository.save(board);
}
https://dao-blog.tistory.com/84
[Spaceplorer.com] 웹 프로젝트 개요 3/4-
글 요약 이름 : Spaceplorer 개요 : 여러 행성 간 여행을 도와주는 패키지여행안내 및 판매 서비스 개발툴 : 개발환경 : 인텔리제이 개발언어 : 자바, 자바스크립트 프레임워크 : 스프링부트, 부트스
dao-blog.tistory.com
'프로젝트' 카테고리의 다른 글
[Spaceplorer.com] Entity와 기능 개발 - 1 (0) | 2024.03.12 |
---|---|
[Spaceplorer.com] 소셜 로그인-1 3/6 (0) | 2024.03.07 |
[Spaceplorer.com] 웹 프로젝트 개요 3/4- (0) | 2024.03.04 |
스파르타 코딩클럽 미니 프로젝트 - 8 트렐로 만들기 08/07~ 08/14 중간 보고 (0) | 2023.08.09 |
스파르타 코딩클럽 미니 프로젝트 - 7 SNS 텐스레드 7/17~21 회고 (0) | 2023.07.24 |