셔틀버스 자동화 웹 배포

chaean·2023년 5월 30일
0

크롤링

목록 보기
3/3

자동화 프로그램을 만들면서 새로 배웠던 방식이나 시간을 많이 잡아먹은 부분에 대해 기록을 남겨보려고한다
exe파일로도 만들어보고 flutter/dart를 통해 앱으로도 만들어봤으나 무료로 배포할 수 있는 방법이 없어 웹으로 배포하기 위해 flask를 사용해보았다.

1. 모달창

<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h1 class="modal-title fs-5" id="exampleModalLabel">버스 시간표 + 좌석표</h1>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <pre>!!월,화,수,목과 금요일 운행 시간표가 다르니 참고할 것!!
좌석이 우로 정렬일 수도 있음료

등교         하교             운전석
07:50       09:10           1  2     3 4
08:00       09:20           5  6     7 8
08:10       09:30           9 10    11 12
08:20       09:40          13 14    15 16
08:30       09:50          17 18    19 20
08:40       10:00          21 22    23 24
08:50       10:10          25 26    27 28
09:00       10:20          29 30    31 32
09:10       10:30          33 34    35 36
09:20       10:40          37 38    39 40
09:30       10:50          41 42    43 44
09:40       11:00
09:50       11:20
10:00       11:40
10:10       12:00
10:20       13:00
10:30       13:20
10:40       13:40
10:50       14:00
11:00       14:20
11:10       14:40
11:20       15:00
11:30       15:10
11:40       15:20
11:50       15:30
12:00       15:40
12:20       16:00
12:40       16:30
            17:00
            17:10
            17:20
            17:30
            18:00
            18:30
            19:00</pre>
                </div>
                <a class="btn btn-outline-success" href="https://ibook.daejin.ac.kr/Viewer/RZIJ2VV0YFLY" target="_blank" role="button">운행 시간표 IBOOK</a>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
                </div>
            </div>
        </div>
    </div>
 <script>
        const myModal = document.getElementById('myModal')
        const myInput = document.getElementById('myInput')

        myModal.addEventListener('shown.bs.modal', () => {
            myInput.focus()
        })
    </script>

모달창 만들고 버튼을 통해 UI에 보여지는지 안보여지는지에 대한 코드

2. Flask 렌더링 방식

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/fail')
def fail():
    return render_template('fail.html')

와 같은 방식으로 html에서 form action="/fail"을 통해 /fail로 들어오면
return render_template('fail.html')을 통해 fail.html을 렌더링 해줌

3. 실행 시 새로고침 및 동작

    <script>
        $(document).ready(function() {
            $('#myForm').submit(function(event) {
                event.preventDefault(); // 폼 제출 이벤트 기본 동작 막기
                const formData = {
                    _id: $('#_id').val(),
                    _pw: $('#_pw').val(),
                    _upTime: $('#_upTime').val(),
                    _downTime: $('#_downTime').val(),
                    _upSeat: $('#_upSeat').val(),
                    _downSeat: $('#_downSeat').val(),
                    option: $('input[name="_option"]:checked').val()
                };
                $.ajax({
                    url: $(this).attr('action'),
                    type: $(this).attr('method'),
                    data: formData,
                    // 상태코드가 200일때 발생
                    success: function(response) {
                        Swal.fire({
                            html: response.message + '<br>' +
                                '<a class="btn btn-outline-success" href="https://daejin.unibus.kr/#/" target="_blank" role="button">예약 확인</a>',
                            icon: 'warning',
                            confirmButtonText: '확인'
                        });
                    },
                    // 상태코드가 200이 아닐때 발생
                    error: function(response) {
                        console.log(response)
                        Swal.fire({
                            title: '오류',
                            html: '처리 중에 오류가 발생했습니다.<br>',
                            icon: 'error',
                            confirmButtonText: '확인'
                        });
                    }
                });
            });
        });
    </script>

ajax를 통해 비동기처리를 했고 SweetAlert를 통해 alert창을 구현했다.
버튼을 클릭할 시 /submit의 함수를 실행하게 되고

@app.route('/submit', methods=['POST'])
def submit():
    if request.method == 'POST' :
        _id = request.form.get('_id')
        _pw = request.form.get('_pw')
        _upTime = request.form.get('_upTime')
        _downTime = request.form.get('_downTime')
        _upSeat = request.form.get('_upSeat')
        _downSeat = request.form.get('_downSeat')
        _option = request.form.get('option')
        response = {'message' : ''}
        # 일반 신청
        if _option == 'a':
            if _upTime != '':
                if _upSeat == '':
                    response['message'] = '등교 좌석이 입력되어있지 않습니다.'
                    return jsonify(response)
                else :
                    if (int(_upSeat) < 1) or (int(_upSeat) > 44):
                        response['message'] = '존재하지않는 좌석 번호 입니다.'
                        return jsonify(response)
            if _downTime != '':
                if _downSeat == '':
                    response['message'] = '하교 좌석이 입력되어있지 않습니다.'
                    return jsonify(response)
                else :
                    if (int(_downSeat) < 1) or (int(_downSeat) > 44):
                        response['message'] = '존재하지않는 좌석 번호 입니다.'
                        return jsonify(response)
            if _upSeat != '':
                if _upTime == '':
                    response['message'] = '등교 시간이 입력되어있지 않습니다'
                    return jsonify(response)
            if _downSeat !='':
                if _downTime == '':
                    response['message'] = "하교 시간이 입력되어있지 않습니다."
                    return jsonify(response)

            run(_id=_id, _pw=_pw, _upTime=_upTime, _downTime=_downTime, _upSeat=_upSeat, _downSeat=_downSeat)
            if ("OK" == upReserveText) or ("OK" == downReserveText):
                response['message'] = '정상적으로 처리되었습니다.'
                return jsonify(response)
            else:
                response['message'] = resultMsg
                return jsonify(response)

        # 예약 신청
        elif _option == 'b':
            if _upTime != '':
                if _upSeat == '':
                    response['message'] = '등교 좌석이 입력되어있지 않습니다.'
                    return jsonify(response)

            if _downTime != '':
                if _downSeat == '':
                    response['message'] = '하교 좌석이 입력되어있지 않습니다.'
                    return jsonify(response)

            if _upSeat != '':
                if _upTime == '':
                    response['message'] = '등교 시간이 입력되어있지 않습니다'
                    return jsonify(response)
            if _downSeat !='':
                if _downTime == '':
                    response['message'] = "하교 시간이 입력되어있지 않습니다."
                    return jsonify(response)
            schedule_job(_id=_id, _pw=_pw, _upTime=_upTime, _downTime=_downTime, _upSeat=_upSeat, _downSeat=_downSeat)
            response['message'] = '예약을 완료했습니다.'
            return jsonify(response)

        # 아무것도 안눌렀을 때
        else:
            response = {'message': '신청 버튼을 눌러주세요'}
            return jsonify(response)


    else :
        return render_template('index.html')

app.py의 코드가 실행된다. option이 a(일반신청) b(예약신청)인지에 따라 신청하는 방식이 달라진다.

4. 예약 신청 (BackgroundScheduler)

def schedule_job(_id, _pw, _upTime, _downTime, _upSeat, _downSeat):

    job_id = f'{_id}_bus_reserve'
    job_date = datetime.today().replace(hour=22, minute=00, second=1)

    if job_date < datetime.now():  # 오늘 22시 1초가 이미 지났다면 내일 22시 1초로 설정
        job_date += timedelta(days=1)

    for job in scheduler.get_jobs():
        if job.id == job_id:
            scheduler.remove_job(job_id)

    scheduler.add_job(run, 'date', run_date=job_date, id=job_id, args=[_id, _pw, _upTime, _downTime, _upSeat, _downSeat])
    print(scheduler.get_jobs())
    if not scheduler.running:
        scheduler.start()

이 부분이 꽤 많은 시간을 잡아먹었다.

전역변수 오류

스케쥴을 다르게 등록해도 마지막에 등록한 입력값들로 모든 스케쥴의 값이 통일되는 문제가 발생했고
예약에 사용되는 값들을 전역변수로 사용했기때문에 발생하는 문제였다.
파라미터로 변수들의 값을 받아오니 해결되는 문제였고

예약 날짜 수정

실행되는 시간이 22시 01초가 넘었다면 days의 값에 1을 더해 다음날에 예약이 되도록 구현했다.

스케줄러 삭제 및 등록

같은 id로 이미 등록된 스케쥴이 있으면 삭제하고 다시 등록하는 로직을 구현했다.

scheduler.start() 중복 실행

스케쥴러가 한번 실행되고 중복 실행되면 오류가 발생했기때문에 한번만 실행할 수 있는 로직 또한 구현했다.

5. 배포

배포하기 위해서는 내가 어떤 라이브러리와 프레임워크를 사용하고있는지requirements.txt 파일에 정의를 해놓아야한다.

pip3 freeze > requirements.txt

터미널에 해당 명령어를 입력하면 모든 라이브러리와 프레임워크가 requirements.txt에 자동으로 등록된다.

PaaS 클라우드 서비스인 Cloudtype을 통해 배포했고
가비아에서 도메인을 구매하고 Cloudflare를 통해 네임서버를 변경하고 DNS를 등록하여 도메인의 이름도 변경하였다.

'
'
'
'
'
'
간단한 기능을 하는 사이트였지만 직접 배포해보기도 하고
프론트엔드를 조금 공부해보며 반응형 웹을 어떻게 디자인하는지에 대한 개념도 익히게 되었다. 프론트와 백이 어떻게 통신하는지 흐릿하게라도 큰 틀을 잡을 수 있는 기회였다.

profile
컴퓨터공학 학부생

0개의 댓글