Spring boot/Bank App

19. 출금 기능 만들기

H_u 2024. 8. 9. 17:05
728x90
반응형
SMALL

0. 작업 순서

  1. withdrawal.jsp 파일 생성 및 코드 추가
  2. 출금 화면 요청 및 기능 구현
  3. 전체 코드 확인
  4. 디버그 모드 동작 시켜 보기

1. withdrawal.jsp 파일 생성 및 코드 추가

withdrawal.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!-- header.jsp -->
<%@ include file="/WEB-INF/view/layout/header.jsp"%>

<!-- start of context.jsp(xxx.jsp) -->
<div class="col-sm-8">
	<h2>출금 요청(인증)</h2>
	<h5>Bank App에 오신걸 환영합니다.</h5>

	<!-- 예외적으로 로그인은 보안때문에 post로 던지자 -->
	<form action="/account/withdrawal" method="post">
		<div class="form-group">
			<label for="amount">출금 금액:</label> <input type="number" class="form-control" placeholder="Enter amount" id="amount" name="amount" value="1000">
		</div>
		<div class="form-group">
			<label for="wAccountNumber">출금 계좌 번호:</label> <input type="text" class="form-control" placeholder="Enter account number" id="wAccountNumber" name="wAccountNumber" value="1111">
		</div>
		<div class="form-group">
			<label for="wAccountPassword">출금 계좌 비밀 번호:</label> <input type="password" class="form-control" placeholder="Enter password" id="wAccountPassword" name="wAccountPassword" value="1234">
		</div>
		<div class="text-right">
			<button type="submit" class="btn btn-primary">출금 요청</button>
		</div>
	</form>

</div>
<!-- end of col-sm-8 -->
</div>
</div>
<!-- end of context.jsp(xxx.jsp) -->

<!-- footer.jsp -->
<%@ include file="/WEB-INF/view/layout/footer.jsp"%>

결과 화면 확인

2. 출금 화면 요청 및 기능 구현

WithdrawalDTO
package com.tenco.bank.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class WithdrawalDTO {

	private Long amount;
	private String wAccountNumber;
	private String wAccountPassword;

}
AccountController
/**
 * 출금 페이지 요청
 * 
 * @return withdrawal.jsp
 */
@GetMapping("/withdrawal")
public String withdrawalPage() {
    User principal = (User) session.getAttribute(Define.PRINCIPAL);
    if (principal == null) {
        throw new UnAuthorizedException(Define.NOT_AN_AUTHENTICATED_USER, HttpStatus.UNAUTHORIZED);
    }
    return "account/withdrawal";
}

@PostMapping("/withdrawal")
public String withdrawalProc(WithdrawalDTO dto) {
    User principal = (User) session.getAttribute(Define.PRINCIPAL);
    if (principal == null) {
        throw new UnAuthorizedException(Define.NOT_AN_AUTHENTICATED_USER, HttpStatus.UNAUTHORIZED);
    }
    // 유효성 검사 (자바 코드를 개발) --> 스프링 부트 @Valid 라이브러리가 존재
    if (dto.getAmount() == null) {
        throw new DataDeliveryException(Define.ENTER_YOUR_BALANCE, HttpStatus.BAD_REQUEST);
    }
    if (dto.getAmount().longValue() <= 0) {
        throw new DataDeliveryException(Define.W_BALANCE_VALUE, HttpStatus.BAD_REQUEST);
    }
    if (dto.getWAccountNumber() == null || dto.getWAccountNumber().trim().isEmpty()) {
        throw new DataDeliveryException(Define.ENTER_YOUR_ACCOUNT_NUMBER, HttpStatus.BAD_REQUEST);
    }
    if (dto.getWAccountPassword() == null || dto.getWAccountPassword().trim().isEmpty()) {
        throw new DataDeliveryException(Define.ENTER_YOUR_PASSWORD, HttpStatus.BAD_REQUEST);
    }
    accountService.updateAccountWithdraw(dto, principal.getId());
    return "redirect:/account/list";
}
AccountService
// 한번에 모든 기능을 생각하는건 힘듦
// 1. 계좌 존재 여부를 확인
// 2. 본인 계좌 여부를 확인 -- 객체 상태값에서 비교
// 3. 계좌 비번 확인 		-- 객체 상태값에서 비교
// 4. 잔액 여부 확인		-- 객체 상태값에서 확인
// 5. 출금 처리				-- update
// 6. 거래 내역 등록		-- insert(history)
public void updateAccountWithdraw(WithdrawalDTO dto, Integer principalId) {
    // 1.
    Account accountEntity = accountRepository.findByNumber(dto.getWAccountNumber());
    if (accountEntity == null) {
        throw new DataDeliveryException(Define.NOT_EXIST_ACCOUNT, HttpStatus.BAD_REQUEST);
    }
    accountEntity.checkOwner(principalId);
    accountEntity.checkPassword(dto.getWAccountPassword());
    accountEntity.checkBalance(dto.getAmount());
    // 5.
    accountEntity.withdraw(dto.getAmount());
    accountRepository.updateById(accountEntity);
    // 6.
    History history = History.builder()
                    .amount(dto.getAmount())
                    .wAccountId(accountEntity.getId())
                    .wBalance(accountEntity.getBalance())
                    .dAccountId(null)
                    .dBalance(null)
                    .build();
    int rowResultCount = historyRepository.insert(history);
    if (rowResultCount != 1) {
        throw new DataDeliveryException(Define.FAILED_PROCESSING, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
Account
package com.tenco.bank.repository.model;

import java.sql.Timestamp;

import org.springframework.http.HttpStatus;

import com.tenco.bank.handler.exception.DataDeliveryException;
import com.tenco.bank.utils.Define;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class Account {
	
	private Integer id;
	private String number;
	private String password;
	private Long balance;
	private Integer userId;
	private Timestamp createdAt;
	
	// 출금 기능
	public void withdraw(Long amount) {
		// 방어적 코드
		this.balance -= amount;
	}
	// 입금 기능
	public void deposit(Long amount) {
		// 방어적 코드
		this.balance += amount;
	}
	// 계좌 소유자 확인 기능
	public void checkOwner(Integer userId) {
		if (this.userId != userId) {
			throw new DataDeliveryException(Define.NOT_ACCOUNT_OWNER, HttpStatus.BAD_REQUEST);
		}
	}
	// 패스워드 체크
	public void checkPassword(String password) {
		if (!this.password.equals(password)) {
			throw new DataDeliveryException(Define.FAIL_ACCOUNT_PASSWORD, HttpStatus.BAD_REQUEST);
		}
	}
	// 잔액 여부 확인
	public void checkBalance(Long amount) {
		if (balance < amount) {
			throw new DataDeliveryException(Define.LACK_Of_BALANCE, HttpStatus.BAD_REQUEST);
		}
	}
}
AccountRepository
package com.tenco.bank.repository.interfaces;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import com.tenco.bank.repository.model.Account;

// AccountRepository 인터페이스와 account.xml 파일을 매칭 시킨다.
@Mapper
public interface AccountRepository {
	
	public int insert(Account account);
	public int updateById(Account account);
	public int deleteById(Integer id);
	
	// interface 파라미터명과 xml에 사용할 변수명을 다르게 사용해야 된다면 @param 어노테이션을
	// 사용할 수 있다. 그리고 2개 이상의 파라미터를 사용할 경우 반드시 @param 어노테이션을 사용하자!
	public List<Account> findByUserId(@Param("userId") Integer principalId);
	// --> account number 값으로 계좌 정보 조회
	public Account findByNumber(@Param("number") String id);
}
account.xml
<select
    id="findByNumber"
    resultType="com.tenco.bank.repository.model.Account">
    select * from account_tb where number = #{number}
</select>
<update id="updateById">
    update account_tb set number = #{number}, password = #{password},
    balance = #{balance}, user_id = #{userId} where id = #{id}
</update>
history.xml
<insert id="insert">
    insert into history_tb(amount, w_balance, d_balance, w_account_id, d_account_id)
    values(#{amount},#{wBalance},#{dBalance},#{wAccountId},#{dAccountId})
</insert>
728x90
반응형
SMALL