모바일 건강검진 예약 시스템 구현

예약 프로세스

  1. 홈페이지에서 '검진 예약' 선택 → 패키지 목록 페이지 이동
  2. 패키지 선택 → 상세 정보 페이지 이동
  3. '즉시 예약' 클릭 → 예약 페이지 이동
  4. 휴대폰 번호 입력 → 인증번호 전송
  5. 인증번호 입력 → 예약 완료

예약 페이지 구현

패키지 정보 표시

<script>
const packageId = new URL(location.href).searchParams.get("id");

const app = new Vue({
  el: '#app',
  data: {
    packageData: {},
    reservation: {
      packageId: packageId,
      gender: 'M'
    }
  },
  mounted() {
    axios.get(`/packages/${packageId}`)
      .then(response => this.packageData = response.data);
  }
});
</script>

휴대폰 인증

function validatePhone(phone) {
  return /^1[3-8]\d{9}$/.test(phone);
}

// Vue 메서드
sendAuthCode() {
  if (!validatePhone(this.reservation.phone)) {
    alert('유효한 휴대폰 번호를 입력하세요');
    return;
  }
  // 인증번호 전송 및 타이머 로직
}

날짜 선택기

<script>
const dateSelector = new Calendar({
  trigger: '.date-picker',
  minDate: addDays(new Date(), 1),
  maxDate: addDays(new Date(), 30)
});

function addDays(date, days) {
  const result = new Date(date);
  result.setDate(result.getDate() + days);
  return result;
}
</script>

예약 요청

submitReservation() {
  axios.post('/reservations', this.reservation)
    .then(response => {
      if (response.data.success) {
        location.href = `/success?id=${response.data.id}`;
      }
    });
}

백엔드 처리

인증번호 컨트롤러

@RestController
@RequestMapping("/auth-codes")
public class AuthCodeController {
  
  @PostMapping("/send")
  public ResponseEntity<?> sendCode(@RequestParam String phone) {
    String code = generateCode(4);
    redisTemplate.opsForValue().set(
      phone + "_RESERVE", 
      code, 
      5, TimeUnit.MINUTES
    );
    // SMS 전송 로직
    return ResponseEntity.ok().build();
  }
}

예약 컨트롤러

@PostMapping("/reservations")
public ResponseEntity<ReservationResult> createReservation(
  @RequestBody ReservationRequest request) {
  
  String storedCode = redisTemplate.opsForValue()
    .get(request.getPhone() + "_RESERVE");
  
  if (!request.getAuthCode().equals(storedCode)) {
    return ResponseEntity.badRequest().build();
  }
  
  ReservationService.Result result = reservationService
    .processReservation(request);
  
  return ResponseEntity.ok(result);
}

예약 서비스

@Service
public class ReservationService {

  public Result processReservation(ReservationRequest request) {
    Date selectedDate = parseDate(request.getDate());
    Schedule schedule = scheduleRepo.findByDate(selectedDate);
    
    if (schedule == null || schedule.isFull()) {
      return new Result(false, "예약 불가");
    }
    
    Member member = memberRepo.findByPhone(request.getPhone());
    if (member == null) {
      member = registerNewMember(request);
    }
    
    if (hasDuplicateReservation(member, request)) {
      return new Result(false, "중복 예약");
    }
    
    Reservation newReservation = createReservation(member, request);
    schedule.incrementReservations();
    
    return new Result(true, newReservation.getId());
  }
}

예약 완료 페이지

<script>
const app = new Vue({
  data: { reservationDetails: {} },
  created() {
    axios.get(`/reservations/${getUrlParam('id')}`)
      .then(res => this.reservationDetails = res.data);
  }
});
</script>

<div>
  <p>예약자: {{reservationDetails.memberName}}</p>
  <p>패키지: {{reservationDetails.packageName}}</p>
  <p>예약일: {{reservationDetails.date}}</p>
</div>

예약 상세 조회

@GetMapping("/reservations/{id}")
public Map<String, Object> getReservationDetails(
  @PathVariable int id) {
  
  return reservationRepo.findDetailsById(id)
    .orElseThrow(NotFoundException::new);
}

태그: vuejs SpringBoot Redis axios MyBatis

7월 10일 16:57에 게시됨