파이썬 + 셀레니움을 활용한 크롤링 (나사이미지)

https://eol.jsc.nasa.gov/SearchPhotos/photo.pl?mission=ISS068&roll=E&frame=41445 

 

 

Astronaut Photo ISS068-E-41445

Spacecraft nadir point: 17.8° S, 71.2° W Photo center point: Photo center point by machine learning: Nadir to Photo Center: Spacecraft Altitude: 226 nautical miles (419km)

eol.jsc.nasa.gov

 

 

0) Selenium 간단 설명

https://selenium-python.readthedocs.io/getting-started.html

 

 

 

1) 준비물

1 :  크롬드라이버

자신에 맞는 드라이버 다운로드 : https://chromedriver.chromium.org/downloads

내가 쓰는 크롬 버전 확인 : 크롬 우측 상단 점3개 > 설정 > 크롬정보

 

 2 :  작업용 폴더

바탕화면에 작업용 폴더 만들고 드라이버를 그 폴더에다가 넣어준다.

 

3: 셀레니움 설치

pip install selenium

 

 

2) 코드작성

1: 임포트하기 

from selenium import webdriver # 웹열때
from selenium.webdriver.common.keys import Keys # 키 가져오기용
from selenium.webdriver.common.by import By # Xpath이용
import time # 슬립걸때 사용
import urllib # 다운로드

 

 

2: 코드 생각해보기 

 

# 41445 번부터 41558번 까지 다운받고싶음 
# https://eol.jsc.nasa.gov/SearchPhotos/photo.pl?mission=ISS068&roll=E&frame=41445 여기에 들어가서
# 큰 화소수 다운 받아야함   => class="btn btn-light" 버튼 눌러서 모달창 띄우기 
# 모달창에서 이미지주소 긁어서 urllib으로 다운로드함 

# 다운받았으면 페이지 번호 1올려야함 => num += num을 반복 -> while문 써야함

 

 

눌러야하는 부분 xpath 확인하기

 

# 41445번째 xpath : //*[@id="wrap"]/div/div/div[4]/div[1]/a[1]

# 41446번째 xpath : //*[@id="wrap"]/div/div/div[4]/div[1]/a[1]

각 페이지별 고화소 픽셀 위치선택하는 xpath는 동일함

 

# 41445번째 xpath : //*[@id="myModal1"]/div/div/div[2]/img
# 41446번째 Xpath : //*[@id="myModal1"]/div/div/div[2]/img   

각 페이지 모달창에 뜬 사진의 xpath가 동일함

=> Xpath 고정값으로 두고 페이지의 num만 바꾸면 됨

 

 

 

 

3: 코드 작성해보기

# 41445 번부터 41558번 까지 다운받고싶음 
# https://eol.jsc.nasa.gov/SearchPhotos/photo.pl?mission=ISS068&roll=E&frame=41445 여기에 들어가서
# class="btn btn-light" 버튼 눌러서 모달창 띄우기 
# 모달창에서 이미지주소 긁어서 urllib으로 다운로드하기 
# 41445번째 xpath : //*[@id="myModal1"]/div/div/div[2]/img
# 41446번째 Xpath : //*[@id="myModal1"]/div/div/div[2]/img   xpath는 동일하니 num만 바꾸면 되겠네?
# 다운받았으면 num += num을 반복 -> while문 써야함


# 초기번호 페이지번호설정 

num = 41445  # 명시적으로 page_num으로 하는게 좋으나 간단하게 num으로 설정해둠

#화면열기
driver = webdriver.Chrome() # while 안에 넣어버리면 계속 새창이 뜸


while num < 41559 :
    print(num)

    # 다운받을 화면
    driver.get('https://eol.jsc.nasa.gov/SearchPhotos/photo.pl?mission=ISS068&roll=E&frame={0}'.format(num))
    time.sleep(2.1)
    print("화면열기 성공", num)

    # 화소선택버튼 찾기
    search = driver.find_element(By.XPATH, '//*[@id="wrap"]/div/div/div[4]/div[1]/a[1]')
    search.click()
    print("화소선택 버튼 누리기 성공", num)

    # image 주소 가져오기
    img_url = driver.find_element(By.XPATH, '//*[@id="myModal1"]/div/div/div[2]/img' ).get_attribute('src')
    print("주소 가져오기 성공", num)

    # image 다운로드
    urllib.request.urlretrieve(img_url, str(num)+'.png')
    print("다운로드 성공", num)
    time.sleep(5) #다운받는 시간 확보
    num = num + 1
 
    print(num)

 

 

다음에는 24장 모아서 1초짜리 영상으로 자동화 해봐야겠다

24fps 영상 자동화

 

 

이전 Signup에는 Default User를 사용함 이번 버젼은 User를 커스텀하겠음

 

 

결과적으로 이것만 추가됨

 

from django import forms
drom django.contrib.auth.forms. import UserCreationForm
from django.contrib.auth import get_user_model # user 정보 알아서 빼주는 함수

# models.py 에서 Foreignkry가 user 일때만 세팅스.어스_유저_모델
# accounts/urls.py
from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    mbti = models.CharField(max_length=4)
    # 아무값 채워넣으면 됨
    # 아무값 채워넣으면 됨



# Settings 맨밑에다 이거 추가
AUTH_USER_MODEL = 'accounts.User'

 

https://docs.djangoproject.com/en/4.1/topics/auth/default/ 

 

 

 

 

 

 

1:N 관계

User-table 하나에서 여러개의 Question-table, 여러개의 reply-table 생성가능

User-table : Question-table = 1 : N

User-table : reply-table.      = 1 : N

 

Question-table : reply-table = 1:N

 

 

<User-table>

id(seq) username password mb

  ti
1 홍길동 1234  
2 민수 123  
3 민아 1234  

<Question-table>

id(seq) Question user_id
1 오늘의 메뉴는? 1
2 오늘 날씨는? 2
3 내일 뭐하지? 3

<reply-table>

id 내용 Que_id user_id
1 햄버거 1(오늘메뉴는?) 3 (민아)
2 맑음 2 1 (홍길동

 

 

 

 

 

 

https://itcreator.tistory.com/129

이어서 하기

 

이전버전은 USER는 DB를 만들지 않아도 따로 생성되었음 (물론 커스터마이징 가능하지만 그건 나중에)

이번은 이전설정과 거의 비슷하게 한번 더 앱을 만들어 보고 게시판/댓글 DB 까지 다뤄보기

 

1.  기초설계

1. ctrl + shift + ~ 터미널 창 열어주기

2. board 앱 폴더 생성 및 등록

# root 기준  터미널 명령어

python manage.py startapp <app 이름>

mkdir -p <app 이름>/templates/<app 이름>  # 템플릿 생성

cd  <app 이름>/templates/<app 이름>  # 최하단 까지 이동

touch detail.html form.html index.html

#detail 게시판글 자세히보기  
#form 게시판 작성 폼
#index 게시글 리스트

 

3. urls.py 및 forms.py 생성

# 다시 최상단 으로 돌아와서( 보통 cd .. 3번 하면됨)

Touch <app이름>/urls.py    # (..../<app이름> 으로 들어오는 통로 열어주기)

Touch <app이름>/forms.py   

# Form 이 친구는 왜만드는거임?
   # 1. 유효성 체크 (validation check)
   # 2. HTML 안에 인풋태그 만들기 귀찮 
   # 3. 저장할 때 request.POST에서 일일이 꺼내기 귀찮아서
   # 이런 작업을 Form 하게 만들려고 미리 폴더 생성

 

2.  board  > urls.py 세팅해주기    (앱이름을 board 로 할 경우)

from django.urls import path
from . import views

app_name = 'board'
urlpatterns = [
    # articles/create/
    path('create/', views.create_article, name='create_article'),    # url에 articles/create/으로 접근이 온다면 => views 에 있는 create_article 함수 실행 / 그리고 이 과정을 create_article 이 라고 칭하겠다. 
    # articles/
    path('', views.article_index, name='article_index'),
    # articles/1/
    path('<int:article_pk>/', views.article_detail, name='article_detail'),
    # articles/1/update/
    path('<int:article_pk>/update/', views.update_article, name='update_article'),
    # articles/1/delete/
    path('<int:article_pk>/delete/', views.delete_article, name='delete_article'),

 

 

3. Model 단 (board > models.py)

from django.db import models
from django.conf import settings

class Article(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) # settings.AUTH_USER_MODEL 파이썬이 이렇게 써달래
    title = models.CharField(max_length=100)									# on_delete=models.CASCADE 이게 지워지면 안에 다른것도 다 지워달라 
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

class Comment(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    content = models.CharField(max_length=200)
    # foreign key 쓰면 필드명에 _id 붙이지 않기.
    # migrate 하면 알아서 뒤에 _id가 붙음
    article = models.ForeignKey(Article, on_delete=models.CASCADE)
    Created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

 

 

3-1 . Form 단 (board > forms.py)

from django import forms
from .models import Article, Comment
class ArticleForm(forms.ModelForm):
    
    class Meta:
        model = Article
        #fields= '__all__'
        exclude = ('user',) # user는 Article의 FK라 article테이블의 user의 키가 
        					# User테이블의 pk값이 없는걸 넣을려고 하면 DB에서 막음 (무결성)
       
class CommentForm(forms.ModelForm):
    content = forms.CharField(min_length=2, max_length=200,
    widget=forms.TextInput(attrs={'autofocus': True})
    )
    class Meta:
        model = Comment
        # fields = ('content',)
        exclude = ('article', 'user')

model. form 끝냈으면 마이그레이트 시작

python manage.py makemigrations board

python manage.py migrate board

 

 

4. Control 단 (board > views.py)

1. 쉬운 버전용 필요한 클래스 import 하기

# 화면구성, 화면이동, 객체를 받거나 없으면 404에러를 터트려줌 (이거 없으면 서버 문제가 아닌데도 500 서버단 에러를 터트림)
from django.shortcuts import render, redirect, get_object_or_404

# get, post등 url 접근 방식에 따라 분류해서 허용하겠다
from django.views.decorators.http import (require_http_methods, require_POST, require_safe, )

# 로그인 상태에만 해당 함수 실행가능
from django.contrib.auth.decorators import login_required

# DB 불러오기
from .models import Article, Comment

# 앞서 만듬 form 불러오기
from .forms import ArticleForm, CommentForm

 

2. create_article 작성

@login_required  # 로그인이 되어야 이 작업가능
@require_http_methods(['GET', 'POST'])  # GET, POST로 들어오는 경우
def create_article(request):   #request 요청 받은 정보 전부 인수로 전달한뒤 함수 실행
    
    if request.method == 'POST': # POST라면(게시글 등록버튼 누를때)
        form = ArticleForm(request.POST) # POST로 쏘는 내용물 채워서 form에 저장
        if form.is_valid():  # 값이 유효하다면
            article = form.save(commit=False) # 잠깐 저장은 하지말아봐  # article부터 만들어야하는데 save를 하면 오류터짐
            article.user = request.user  # 페이징 요청자의 정보를 글작성자의 정보로 넣어줌
            article.save()  # 게시글 저장(내장함수)
            return redirect('board:article_detail', article.pk)   # 글번호와 함께 detail() 함수실행

    else:   # GET으로 왔다면(페이지를 요청할때)
        
        form = ArticleForm()   # form.py에서 만든걸 form으로
    context = {'form': form }    # context에 담아서 
   
    return render(request, 'board/form.html', context)  # form.html로 넘겨줌

 

3. read

@require_safe # get, head 요청만 허용하겠다.
def article_index(request):
    articles = Article.objects.all()   # DB의 article의 모든 값을 articles에 넣어줌
    context = {'articles': articles, }   # 그걸 context에 담아서
    return render(request, 'board/index.html', context)   # index.html로 넘겨줌

@require_safe
def article_detail(request, article_pk):   # 몇번째 게시글인지 알 수 있게 article_pk 받아야함
    article = get_object_or_404(Article, pk=article_pk)  # db에서 객체를 받던가 404를 띄워라! why? DB에 없는걸 요청한다면 니 잘못
    form = CommentForm() # 댓글보여줄려고-> detail.html에 board/_comment_form.html에 form 쓸려고
    context = {     # 게시글 정보와 댓글을 context에 담아서
        'article': article,
        'form': form
    }
    return render(request, 'board/detail.html', context)   # detail.html로 보냄

 

4. update

@login_required  # 로그인 필요
@require_http_methods(['GET', 'POST'])   # GET, POST로 오는 경우
def update_article(request, article_pk):
    article = get_object_or_404(Article, pk=article_pk)   # 받은 번호의 게시글 정보를 DB에서 찾아 article에 넣어줌
   
    if request.user != article.user:			 # 작성자 아니면
        return redirect('board:article_detail', article.pk)    # 게시글 번호와함께 detail.html로 보내버림

    if request.method == 'POST':   # POST인 경우(게시글 수정 버튼을 누를때)
        form = ArticleForm(request.POST, instance=article)  # instance=student 가 없으면 수정이 아니라 새 값으로 저장되어버림 + 이전정보 넣어주기
        if form.is_valid():  # form이 유효하다면
            # 기존에 저장된 user_id 갱신할 필요가 없기때문에 commit=False 필요 X
            article = form.save()   # 저장해서 article에 넣어주기
            return redirect('board:article_detail', article.pk)   # 게시글 번호 주면서 detail() 함수실행
    else:   # get방식이라면 (이 페이지를 요청할때)
        form = ArticleForm(instance=article)   # # instance=student => form에다가 이전 정보 넣기
    context = {'form': form}   # context에 담아서
    return render(request, 'board/form.html', context)   # form.html로 보냄

 

5. Delete

@login_required   # 로그인 필요
@require_POST   # POST인 경우(url로 들어오는거 막겠다/ 제출버튼으로 토큰 던지면서 와라)
def delete_article(request, article_pk):  # 요청받은 정보와 게시글 번호를 주면서
    article = get_object_or_404(Article, pk=article_pk)  # 번호에 맞는 Article DB에서 객체를 꺼내서 article에 주던가 404로 
    if request.user == article.user:   # 요청자와 글쓴이가 같다면 
        article.delete()   	# 게시글 삭제(내장함수) 
        return redirect('board:article_index')   # index로 보내주기
    else:     # 요청자와 글쓴이가 같지않다면
        return redirect('board:article_detail', article.pk)  # 게시글 번호와함께 detail.html로 보냄

 

6. 댓글 (간단하게 CRD만)

@require_POST
def create_comment(request, article_pk):  # 요청 정보와 게시글 번호를 받음
    form = CommentForm(request.POST) # 댓글 제출 버튼시 값들을 form에 넣어줌 
    article = get_object_or_404(Article, pk=article_pk) # 받은 번호의 게시글 정보를 DB에서 찾아 article에 넣어줌

    if form.is_valid():  # form이 유효하다면
       
        comment = form.save(commit=False)    # 완전 저장시 NOT NULL 에러 뜨니까, 직전에 멈춰 주세요.
        comment.article = article       # 여기선 알아서 값 넣어줌
        comment.user = article			 # 여기선 알아서 값 넣어줌
        comment.save()		# 값 저장(내장함수)
    return redirect('board:article_detail', article.pk)  # 게시글 번호 받아서 detail() 함수 실행 
    # comment.article.pk 게시글 번호임

@require_safe # get, head 요청만 허용하겠다.
def article_index(request):   # 요청한값 받음
    articles = Article.objects.all()  Article DB 값 받아서 articles에 넣어줌
    context = {'articles': articles, }   # 그 값을 context 넣고
    return render(request, 'board/index.html', context)  # index.html로 보내줌

@require_safe
def article_detail(request, article_pk):   # 요청정보와 게시글 번호 받음
    article = get_object_or_404(Article, pk=article_pk)  # 받은 번호의 게시글 정보를 DB에서 찾아 article에 넣어줌
    form = CommentForm() # 댓글보여줄려고-> detail.html에 board/_comment_form.html에 form 쓸려고
    context = {
        'article': article,
        'form': form
    }
    return render(request, 'board/detail.html', context)


@login_required
@require_POST
def delete_comment(request, article_pk, comment_pk):
    comment = get_object_or_404(Comment, pk=comment_pk)
    article = get_object_or_404(Article, pk=article_pk) # article.pk 를 쓰기위해서 만듬
    
    if request.user == comment.user:
        comment.delete()
    return redirect('board:article_detail', article.pk)
    
    # article_pk 안쓰는 이유는 사용자를 믿을수없어서.
    # 사용자가 보낸거 _pk 구분한다음  내부적으로 확인해서 믿을하면 .pk 써도 무방

 

 

총정리

@login_required
@require_http_methods(['GET', 'POST'])
def create_article(request):
    
    if request.method == 'POST': 
        form = ArticleForm(request.POST) 
        if form.is_valid():
            article = form.save(commit=False)
            article.user = request.user 
            article.save()
            return redirect('board:article_detail', article.pk)

    else: 
        form = ArticleForm()
    context = {'form': form }
    return render(request, 'board/form.html', context)

@login_required
@require_http_methods(['GET', 'POST'])
def update_article(request, article_pk):
    article = get_object_or_404(Article, pk=article_pk)
    
    if request.user != article.user:
        return redirect('board:article_detail', article.pk)

    if request.method == 'POST':
        form = ArticleForm(request.POST, instance=article)        
        if form.is_valid():
           
            article = form.save()
            return redirect('board:article_detail', article.pk)
    else:
        form = ArticleForm(instance=article)
    context = {'form': form}
    return render(request, 'board/form.html', context)

@login_required
@require_POST
def delete_article(request, article_pk):
    article = get_object_or_404(Article, pk=article_pk)
    if request.user == article.user:
        article.delete()
        return redirect('board:article_index')
    else:
        return redirect('board:article_detail', article.pk)



@require_POST
def create_comment(request, article_pk):
    form = CommentForm(request.POST)
    article = get_object_or_404(Article, pk=article_pk)

    if form.is_valid():
      
        comment = form.save(commit=False) 
       
        comment.article = article
        comment.user = article
        comment.save()
    return redirect('board:article_detail', article.pk)
  

@require_safe 
def article_index(request):
    articles = Article.objects.all()
    context = {'articles': articles, }
    return render(request, 'board/index.html', context)

@require_safe
def article_detail(request, article_pk):
    article = get_object_or_404(Article, pk=article_pk)
    form = CommentForm() 
    context = {
        'article': article,
        'form': form
    }
    return render(request, 'board/detail.html', context)



@login_required
@require_POST
def delete_comment(request, article_pk, comment_pk):
    comment = get_object_or_404(Comment, pk=comment_pk)
    article = get_object_or_404(Article, pk=article_pk)
    
    if request.user == comment.user:
        comment.delete()
    return redirect('board:article_detail', article.pk)

 

 https://itcreator.tistory.com/125 를 보고 이 글을 진행하는게 이해가 쉬움 

 

시작하기전에 협업할때 편한 가상화 작업

 

1. ctrl + shift + ~ 터미널 창 열어주고 가상환경설정하기

 

python -m venv <env name> # 가상환경
# Venv 안뜨면 ctrl/command + shift + p 로 인터프리터 열어서 <env이름> 직접 선택

# 수동
(Mac) source venv/bin/activate
(Win) source vent/Scripts/activate
pip install django==3.2.16 django_extensions

pip list  # 상태확인

touch README.md .gitignore # READ, gitignore 사전작업

pip freeze > requirements.txt  # 협업 세팅환경용


# 3.2.16버전이 롱텀이라 안전성높음

 

 

1. 회원가입에 필요한 기초설계

1. account 앱 폴더 생성 및 등록

# root 기준  터미널 명령어

python manage.py startapp <app 이름>

mkdir -p account/templates/account  # 템플릿 생성

cd  account/templates/account  # 최하단 까지 이동

touch login.html signup.html     # html 생성

 

2. urls.py 생성 (..../<app이름> 으로 들어오는 통로 열어주기)

# 다시 최상단 으로 돌아와서( 보통 cd .. 3번 하면됨)

Touch <app이름>/urls.py    # url 이동 잡아주기

 

2.  accounts  > urls.py 세팅해주기    (앱이름을 accounts로 할 경우)

from django.urls import path
from . import views

app_name = 'accounts'

urlpatterns = [
    # accounts/signup/
    path('signup/', views.signup, name='signup'),   # url에 accounts/signup/으로 접근이 온다면 => views 에 있는 signup 함수 실행 / 그리고 이 과정을 signup 이 라고 칭하겠다. 
    # accounts/login/
    path('login/', views.login, name='login'),
    # accounts/logout/
    path('logout/', views.logout, name='logout'),

]

 

3. Control 단 (accounts > Views.py)

1. 쉬운 버전용 필요한 클래스 import 하기

# 화면구성, 화면이동, 객체를 받거나 없으면 404에러를 터트려줌 (이거 없으면 서버 문제가 아닌데도 500 서버단 에러를 터트림)
from django.shortcuts import render, redirect, get_object_or_404 

# DB 생성없이도 account에 필요한 컬럼을 만들어줌, 인증에 필요한 옵션
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm

# 세션을 부여하거나 수거함 (출입증)
from django.contrib.auth import login as auth_login, logout as auth_logout

# get, post에 따른 
from django.views.decorators.http import require_http_methods

 

2. 가장 먼저 signup 함수채우기 

@require_http_methods(['GET', 'POST'])   #GET, POST 요청만 받겠다
def signup(request):   #request 요청 받은 정보 전부 인수로 전달한뒤 함수 실행
    if request.method == 'POST':  # 회원가입 신청서 제출시 작동
        form = UserCreationForm(request.POST)  # user 의 경우 Model괴 Form도 자동으로 해줌 /import만 잘하면됨
        if form.is_valid():   # 내장 검증함수실행 / False 시 13번부터 실행
            user = form.save()   # 유저가 입력한 정보 DB에 저장
            auth_login(request, user) # 회원가입하고 바로 세션부여 채워줌 
            return redirect('board:article_index')   # 바로 게시글 볼 수 있게 리다이렉트해줌
    
    else:   # get으로 온 경우 
        form = UserCreationForm()   # 회원가입 폼
        
    context = {'form': form }  # 폼을 context에 담아서 
    return render(request, 'accounts/signup.html', context)   #받은 정보들을 바탕으로 signup.HTML 화면 구성

3. login 함수 채우기

@require_http_methods(['GET', 'POST'])  # get과 포스트만 받겠다
def login(request):
    if request.method == 'POST':  # POST인 상황 즉 아이디 / 비번 넣고 로그인 버튼 눌렀을때 실행
        form = AuthenticationForm(request, request.POST) # 인증폼은 다른 폼과 인자 구성이 다름 / 지금은 request 인수를 던지는 이유에 대해선 넘어가기로.. 
        if form.is_valid():
            user = form.get_user()  # 이 유저가 누군지 알려줌
            auth_login(request, user ) # 받은 유저정보를 주면 입장 팔찌를 채워줌 파이썬에서 제공해주는 함수 (임포트해야함) 
           #  None/ URL string 들어가있음
            next = request.GET.get('next') # 다른곳에서 튕겨서 온 사람들 /? 이후 URL을 request.GET으로 받음 .get은 값이 없더라도 none으로 받음  
            return redirect(next or 'board:article_index') # next 가 false 면 index로 보내고/ next가 true 면 next 값으로 이동  
    else:  #  GET인 상황 로그인 페이지를 들어오는 상태라면
        form = AuthenticationForm()   # 내장함수를 form으로 받고
    context = {'form': form}   # 내장함수에 받은 내용을 context에 담아 보냄
    return render(request, 'accounts/login.html', context)

4. logout 함수채우기

def logout(request):
    auth_logout(request) # 세션을 지움
    return redirect('board:article_index')  # 게시글 있는곳으로 다시 리다이렉트

 

 

 

 

< 총 정리하자면 >

from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth import login as auth_login, logout as auth_logout
from django.views.decorators.http import require_http_methods

@require_http_methods(['GET', 'POST'])
def signup(request):
    if request.method == 'POST':
        form = UserCreationForm(request.POST)  
        if form.is_valid():
            user = form.save()
            auth_login(request, user) 
            return redirect('board:article_index')
    else:
        form = UserCreationForm()

    context = {'form': form }
    return render(request, 'accounts/signup.html', context)


@require_http_methods(['GET', 'POST'])
def login(request):
    if request.method == 'POST':
        form = AuthenticationForm(request, request.POST) 
        if form.is_valid():
            user = form.get_user()  
            auth_login(request, user ) 
           
            next = request.GET.get('next')
            return redirect(next or 'board:article_index') 
    else:
        form = AuthenticationForm()
    context = {'form': form}
    return render(request, 'accounts/login.html', context)

def logout(request):
    auth_logout(request)
    return redirect('board:article_index')

 

 

https://itcreator.tistory.com/130

2탄 게시판 및 댓글 쉬운버젼 (DB가 추가됨)

 

[Django] Board/comment 게시판/댓글 쉬운버젼

https://itcreator.tistory.com/129 이어서 하기 이전버전은 USER는 DB를 만들지 않아도 따로 생성되었음 (물론 커스터마이징 가능하지만 그건 나중에) 이번은 이전설정과 거의 비슷하게 한번 더 앱을 만들

itcreator.tistory.com

 

1.  Project 폴더 / 앱 설정

 

1. 프로젝트 폴더 생성 과 동시에 manage.py 파일을 만듬.

django-admin startproject  <project 이름> . (뒤에 . 찍어야 현재 폴더 위치에서 생성됨)

2. 그 프로젝트 안에 구성할 앱들 만들기

python manage.py startapp <app 이름>

 

 

2.  가상환경 설정 

메인폴더에서 

python -m venv <env name> # 가상환경



(Mac) source venv/bin/activate

(Win) source vent/Scripts/activate

 

Command + Shift + p > 인터프리터 선택 > 가상환경 선택

venv 가 안떠있으면 터미널 껏다 켜기

READ.md , .gitignore, requirements 생성

pip install django==3.2.16 django_extensions

pip list  # 상태확인

touch README.md .gitignore # READ, gitignore 사전작업

pip freeze > requirements.txt  # 협업 세팅환경용


# 3.2.16버전이 롱텀이라 안전성높음

 

 

3. 뼈대잡기 

사용할 html의 뼈대를 잡음

 

1. 공통적으로 활용할 html 생성

Project > templates > base.html or navbar.html 등등 (include용 메인 html)

install -Dv /dev/null template/base.html

#   (mkdir templates >  touch templates/base.html을 한번에)

 

2. 앱에서 사용할 html 생성

Mkdir -p <app이름>/templates/<app이름>  # -p 는 templates 라는 상위폴더가 없어도 상위폴더까지 같이 생성해줌

cd <app이름>/templates/<app이름>  # cd로 최하단까지이동

touch **.html **2.html **3.html (사용할 html 쭉 나열하면 여러개 생성)

 

 

 

3. 기타 .py 파일 생성

# 다시 최상단 으로 돌아와서( 보통 cd .. 3번 하면됨)

Touch <app이름>/urls.py, forms.py 생성

4. 초기세팅

1. Project>settings에서 해야할 일

 Project > settings > app등록  

*

'django_extensions', # 추가해두면 나중에 python manage.py shell_plus 사용가능

 

 

 

 

 

 

Templates / dirs 등록 >  BASE_DIR / ‘templates’

 

 

(Project) > (App) > views.py > 함수생성

 

base.html > block > tab

 

2. Master urls.py 내용추가

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('<app이름>/', include('<app이름>.urls')),
]

 

3. app이름 >  urls.py

from django.urls import path
from . import views

app_name = '<앱이름>'

urlpatterns = [
    # <앱이름>/create/
    path('create/', views.create, name='create'),
    # <앱이름>/
    path('', views.index, name='index'),
    # <앱이름>/1/
    path('<int:student_pk>/', views.detail, name='detail'),
    # <앱이름>/1/update/
    path('<int:student_pk>/update/', views.update, name='update'),
    # <앱이름>/1/delete/
    path('<int:student_pk>/delete/', views.delete, name='delete'),
]

 

4. app이름 > views.py 세팅

함수정의 하기 

 

from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_http_methods, require_safe, require_POST
from .models import Student
from .forms import StudentForm

@require_http_methods(['GET', 'POST'])
def create(request):
    pass
    
# CRUD에 맞는 필요함수 적기

 

 

4. 서버실행

Manage.py 있는 루트위치에서

python manage.py runserver 

# 나가기 Ctrl + C

 

5. DB 세팅

1. Model.py 작성

app > models.py  / 자바로 치면 DAO

from django.db import models

class Student(models.Model):
    name = forms.CharField(min_length=2, max_length=10)
    title = forms.CharField(min_length=2, max_length=20)

 

2. Forms.py 작성

app > forms.py

# 1. 유효성 체크 (validation check)
# 2. HTML 안에 인풋태그 만들기 귀찮
# 3. 저장할 때 request.POST에서 일일이 꺼내기 귀찮아서 forms.py 만들어서 관리
from django import forms
from .models import Student

class StudentForm(forms.ModelForm):
    name = forms.CharField(min_length=2, max_length=10)
    title = forms.CharField(min_length=2, max_length=20)
    
    class Meta:  # 이 클래스의 메타 데이터 저장용 
        model = Student
        # fields = ('name', 'title')
        fields = '__all__'

 

3. 서버시작

서버실행 (혹시 값이 바뀔면 다시 실행)

python manage.py makemigrations <app이름>

python manage.py migrate <app이름>

# 잘안되면
rm db.sqlite3 <앱이름>/migrations/0*
rm db.sqlite3 accounts/migrations/0* board/migrations/0*  
# 다 지우고다시

 

 

6. 꾸미기

pip install django-bootstrap-v5

# 장고 부트스트랩 추가
pip install django-bootstrap-v5

설명서

https://django-bootstrap-v5.readthedocs.io/en/latest/installation.html

 

1.  실행되는 순서가 다르다. / 관리되는 컨테이너가 다르다 ( Filter -> web container / Interceptor -> Spring container)

  why?

  filter는 자바에서 제공하는 

 interceptor는 스프링에서 제공 -> DispatcherServlet에 의해 호출됨 -> HttpServletRequest, HttpServletResponse 객체를 변경할 수 없다! ( filter는 가능)

 

 

 

2. 어댑터 또는 데코레이터 패턴 적용여부

Filter O

   -> filter의 경우 디스펫치서블렛 불리기 이전이므로 /

서블릿 리퀘스트를 요청받아 -> HTTP서블릿 리퀘스트 -> filter -> chain.doFilter

doFilter 에서 요청(Request)/ 응답(Response) 변경할 수 있음

 

-> Filter에서 선택적으로 요청, 응답 객체를 사용자 정의 구현체로 래핍하여 어댑터(래퍼), 또는 데코레이터 패턴으로 적용가능

 

Interceptor X

 

래퍼 없을땐 1번만 적용됨
래퍼 재정의 해서 반복가능

3. 이러한 이유로

Filter 

< 스프링과 무관하게 전역적으로 처리해야하는 작업, 웹 어플리케이션에 전반적으로 사용되는 기능>

@ 공통된 보안 및 인증

   - 전역적으로 해야하는 보안 검사 (XSS,CSRF 방어 등)

@ 모든 요청에 대한 로깅 또는 감사 (위험 관리)

@ 이미지/ 데이터 압축 및 문자열 인코딩

@ Spring과 분리하려는 모든 기능

 

대표적으로 SpringSecurity가 Filter 기반의 인증 및 인가를 검사 할 수 있다. 따라서 Spring MVC에 종속적이지 않다

 

Interceptor

<클라이언트 요청과 관련되어 전역적으로 처리해야하는 작업>

@ 세부적인 보안 및 인증

  - 클라이언트 요청과 관련된 인증, 인가 작업.

 특정 그룹의 사용자에 대한 기능 제한 등

@API 호출에 대한 로깅 또는 감사

@Controller로 전달되는 정보의 가공

 

 

https://www.youtube.com/watch?v=nQizVrBYCO0&t=559s

'WEB Creator > [Spring]' 카테고리의 다른 글

[Spring] @Controller와 @RestController  (0) 2022.08.31
[Spring] File Download  (0) 2022.08.25
[Spring] File Upload  (0) 2022.08.25

2. 파일 다운로드


1) 우선순위 지정
2) DownloadView.java 기능 사용을 위해 bean으로 등록
3) jsp 페이지에 다운로드용 버튼 하나 만들고 요청경로 지정
4) Controller에서 해당 요청 처리 매핑메서드 만들어 DownloadView의 기능 호출

 

2-1 우선순위 지정

AppServlet > Servlet-context 에 들어가서 해당 코드 입력하기

 

	<!--  InternalResourceViewResolver 보다 우선순위를 높게해서 먼저 다운로드에 관하는
    				beannameViewResolve 실행되게끔 -->
	<beans:bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
		<beans:property name="order" value="0"></beans:property>
	</beans:bean>

	<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
		<beans:property name="order" value="1"></beans:property>
	</beans:bean>

 

 

2-2 Downloadview 기능을 위한 bean등록

 

 

AppServlet > root-context 에 들어가서 해당 코드 입력

<bean id="fileDown" class="com.upload.util.DownloadView" />

 

src/main/java > com.upload.util 패키지 생성후

해당 java 클래스 만들기

 

package com.upload.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.util.FileCopyUtils;
import org.springframework.web.servlet.view.AbstractView;

public class DownloadView extends AbstractView {
	
	public DownloadView() {
		setContentType("application/download; charset=utf-8"); 
	}
	@Override							
	protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {		

		File file = (File)model.get("downloadFile");		
		response.setContentType(getContentType()); 			
		response.setContentLength((int)file.length());		
		
		String fileName = java.net.URLEncoder.encode(file.getName(), "UTF-8"); 
		response.setHeader("Content-Disposition", "attachment;filename=\""+fileName+"\";");  
		response.setHeader("Content-Transfer-Encoding", "binary");
		
		OutputStream out = response.getOutputStream();		
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(file);		
			FileCopyUtils.copy(fis, out);			
		}catch (Exception e) {
			e.printStackTrace();
		}finally {
			if(fis != null) { try {fis.close();}catch(Exception e2) { e2.printStackTrace(); } }
			out.flush();
		}
	}
}

 

 

2-3 jps 페이지에 다운로드용 버튼 만들고 요청경로 지정

 

helloDown.jsp

window.location='/upload/download  요청경로지정

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>Insert title here</title>
</head>
<body>
	<h2> hello down page</h2>
	<h3> 다운을 받고 싶으면 아래 버튼을 누르세요 !!!</h3>
	<button onclick="window.location='/upload/download'"> 다운로드  </button>
</body>
</html>

 

 

 

 

2-4  Controller에서 해당 요청 처리 매핑메서드 만들어 DownloadView의 기능 호출

 

	// 다운로드 버튼 띄운 화면	
	@GetMapping("helloDown")
	public void helloDown() {
		
	}

	// 다운 요청 처리
	@GetMapping("download")
	public ModelAndView down() {
		// 다운 시킬 파일 준비
		File f = new File("C:\\img\\ham.jpg");
		// 생성자 매개변수
		// String viewName 		:	view 이름 -> xml 지정한 downloadView 빈의 id값
		// String modelName		:	파라미터명 지정 (이름) 
		// Object modelObject	:	데이터 (다운 시킬 파일)
		ModelAndView mv = new ModelAndView("fileDown", "downloadFile", f);  //root에 bean으로 fileDown 만듦 그 다음 dowloadFile 키와 f라는 value를 줌 
		return mv;
	}
}

'WEB Creator > [Spring]' 카테고리의 다른 글

[Spring] Filter와 Interceptor의 차이점  (0) 2022.09.08
[Spring] @Controller와 @RestController  (0) 2022.08.31
[Spring] File Upload  (0) 2022.08.25

spring mvc 에서 파일 업로드 다운로드 하는 방법!

 

 

1. 파일 업로드

 

1-1 라이브러리 추가 (pom.xml에 추가)

commons-fileupload, commons-io.jar 추가하기 

 

		<!-- 파일업로드용 라이브러리 2개 추가 -->
		<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
		<dependency>
		    <groupId>commons-fileupload</groupId>
		    <artifactId>commons-fileupload</artifactId>
		    <version>1.4</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
		<dependency>
		    <groupId>commons-io</groupId>
		    <artifactId>commons-io</artifactId>
		    <version>2.6</version>
		</dependency>

1-2 MultipartResolver 추가 (root-context.xml)

	<!--  파일 업로드 -->
	<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<property name="maxUploadSize" value="-1"></property>  <!-- '-1' 은 파일크기 무제한 -->
		<property name="defaultEncoding" value="UTF-8"></property>
	</bean>

 

 

1-3 파일 업로드 view(jsp) 작업 

 

간단하게 파일 uploadForm과  uploadPro 만들고

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>uploadform</title>
</head>
<body>

	<h2> File Upload</h2>  //post!! multipart 로 보내기!!
	<form action="/upload/uploadPro" method="post" enctype="multipart/form-data">
		message : <input type="text" name="msg" /> <br/>
		file 	: <input type="file" name="img" /> <br/>
		message : <input type="submit" value="전송" /> <br/>
	</form>

</body>
</html>



-------------------------------------------------------------------------------
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>upload Pro</title>
</head>
<body>

</body>
</html>

 

 

 

1-4 파일 업로드 컨트롤러 작업 

 

 파일업로드 작업
MultipartHttpServletRequest 인터페이스

* 주요 메서드
Iterator<String>  getFileNames() : 업로드된 파일들의 파라미터 명 리턴
MultipartFile getFile(String name) : 파라미터명이 name인 파일 리턴
List<MultipartFile> getFiles(String name) : 파라미터명이 name인 업로드 파일 정보 목록 리턴

* 작업 순서
- 처리 매핑 메서드에 매개변수 MultipartHttpServletRequest 지정
- request에서 파일 정보 꺼내담기
- 파일 저장 경로 + 새파일명 만들어 File 객체 만들기 (파일 이름 중복 안되게)
- .transferTo() 메서드로 파일 저장

package com.board.controller;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;

import lombok.extern.log4j.Log4j;

@Controller
@RequestMapping("/upload/*")
@Log4j
public class UploadController {
	
	@GetMapping("uploadForm")
	public void upload() {
		log.info("upload form!!!!!!!!!!!!!!");
	}
	
	@PostMapping("uploadPro")
	public void uploadPro(String msg, MultipartHttpServletRequest request) { // msg(text), img(file) 
		log.info("*********upload pro*************");
		log.info("********* msg : " + msg );
		//log.info("********* img content type : " + request.getContentType());
		//log.info("********* img : " + request.getFile("img")); // 파일 정보 꺼내기
		
		try {
			// 전송한 파일 정보 꺼내기
			MultipartFile mf = request.getFile("img");
			log.info("****************** original file name : " + mf.getOriginalFilename());
			log.info("****************** file size : " + mf.getSize());
			log.info("****************** file contentType: " + mf.getContentType());
			
			
			// 파일 저장 경로
			String path = request.getRealPath("/resources/save");
			log.info(" save path : "+ path);
			// 새파일명 생성
			String uuid = UUID.randomUUID().toString().replace("-", "").toUpperCase();  // 하이픈 다 빼버림 대문자로
			log.info("***********uuid : " + uuid);
			// 업로드한 파일 확장자만 가져오기 
			String orgName= mf.getOriginalFilename();
			// beach.jpg   -> .jpg만 두고 나머지는 지움
			String ext = orgName.substring(orgName.lastIndexOf("."));
			
			// 저장할 파일명
			String newFileName = uuid + ext;
			log.info("*****new fileName : " + newFileName);
			
			//저장할 파일 전체 경로
			String imgPath = path + "\\" + newFileName;
			log.info("****imgPath : " + imgPath);
			
			
			// 파일저장
			File copyFile = new File(imgPath);
			mf.transferTo(copyFile);
			
		}catch(IOException e){
			e.printStackTrace();
		}
	}
}

'WEB Creator > [Spring]' 카테고리의 다른 글

[Spring] Filter와 Interceptor의 차이점  (0) 2022.09.08
[Spring] @Controller와 @RestController  (0) 2022.08.31
[Spring] File Download  (0) 2022.08.25

1. 라이브러리 연동

 

https://code.jquery.com/jquery-3.6.0.min.js
https://cdnjs.com/libraries/jquery   - cdn 방식의 라이브럴
1- 다운받아 오프라인으로 작업
2- CDN방식으로 script 태그로 배치

 


2. 선택자 Selector


요소 선택 : $() 안에 문자형 데이터로 CSS 선택자 입력
ex. $("선택자").css("스타일속성명","값");
    $("선택자").attr("속성명","값"); align, tag등

기본 작성방법
#1. 문서 객체 먼저 불러와 선택자 사용 * 2번보단 1번 추천 (오류덜남)

<script>
    $(document).ready(function(){
   		 //jquery 작성 영역
   		 $("선택자").css("스타일속성명","값");
    });
</script>



#2. 즉시 함수로 묶기

    $(function(){
        //jquery 영역
    });

 

2 - 1 기본선택자

전체 선택자  $("*")
아이디 $("#아이디값")
클래스 $(".클래스값")
태그 $("태그명")
여러개 $("선택1, 선택2, 선택3....")
종속 $("태그명#아이디값")$("태그명.클래스값")$("h1.test.abc")
부모 $("선택").parent() .parents() 
하위 $("기준선택 요소선택")
자식 $("선택 > 자식")
  $("선택").children("자식")
형/동생 $("선택").prev() 형               .prevAll()
  $("선택").next() 동생          .nextAll()
  $("선택+ 요소")
전체 형제  $("선택").siblings()
가장 가까운 상위 $("선택").closest("요소")

 

 

2 - 2 속성선택자

선택한 요소를 기준으로 일치하는 속성의 포함여부를 따져 요소를 선택

 

$("선택[속성]")  선택요소중 지정한 속성이 포함된 요소 선택
$("선택[속성^=값]")  속성값이 지정한 값으로 시작하는 요소 선택 
$("선택[속성$=값]") 속성값이 지정한 값으로 끝나는 요소 선택
$("선택[속성*=값]") 속성값이 이러한 값으로 포함하는 요소 선택
$(":type 속성값") input태그 중 type 속성값이 일치하는 요소 선택
$("선택:visible") 보이는 상태의 요소만 선택
$("선택:hidden") 숨긴 상태의 요소만 선택
$("선택:selected") 선택된 상태의 요소만 선택
$("선택:checked") 체크된 상태의 요소만 선택

 

 

2 - 3 탐색선택자


$("선택").not(:제외요소)
$("선택").find(:"요소선택") : 기준으로 선택한 요소중 지정한 요소만 선택

 1. MultipartRequest 객체 생성시, 필요한 인자들
      1. request 내장 객체 (jsp에 이미 있다)
      2. 업로드 될 파일 저장 경로 
      3. 업로드 할 파일 최대 크기
      4. 인코딩 타입 : UTF-8
      5. 업로드된 파일 이름이 같을경우, 덮어씌우기 방지 객체
    

   2. 브라우저에서 보낸 파일 저장할 서버측의 저장 경로
    2-1. PC에 저장(서버쪽X, 서버가 있는 내PC에 저장)
   String path = "C:\\tmp\\"; (pc에 올리면 웹페이지상에서 볼수가없다 )
   
    2-2 서버상에 저장
   String path = request.getRealPath("save"); // 서버상의 save 폴더 실제 경로 찾기
   System.out.println(path);
   3. 업로드 할 파일 최대 크기
   int max = 1024*1024*5;   // 5mb
   4. 인코딩
   String enc = "UTF-8";
   5. 덮어씌우기 방지 객체
   DefaultFileRenamePolicy dp = new DefaultFileRenamePolicy();
   
   6. MultipartRequest 객체 생성
   MultipartRequest mr = new MultipartRequest(request, path, max, enc, dp);
   
   7. 파라미터 받기
   String writer = mr.getParameter("write");
   String sysName = mr.getFilesystemName("upload");   // 업로드 파일 이름
   String orName = mr.getOriginalFileName("upload");   // 파일 원본 이름
   String contentType = mr.getContentType("upload");   // 파일 종류 : 사진, 글....
   
   
   8.DB에 저장
   UploadDAO dao = new UploadDAO();
   dao.insertData(writer,sysName);
   
   
   

<title>image upload</title>
</head>
<body>
   <h1> form page </h1>
      <form action="upload.jsp" method="post" enctype="multipart/form-data">
         작성자 : <input type="text" name="write" /> <br />
         파  일 : <input type="file" name="upload" /> <br />
                <input type="submit" value="전송" />
      
      </form>

</body>
</html>

 

 

 

<title>upload pro</title>
</head>
<%
   request.setCharacterEncoding("UTF-8");
   /*  MultipartRequest 객체 생성시, 필요한 인자들.
   
      1. request 내장 객체 (jsp에 이미 있다)
      2. 업로드 될 파일 저장 경로 
      3. 업로드 할 파일 최대 크기
      4. 인코딩 타입 : UTF-8
      5. 업로드된 파일 이름이 같을경우, 덮어씌우기 방지 객체
      
      */

   // 2. 브라우저에서 보낸 파일 저장할 서버측의 저장 경로
   // 2-1. PC에 저장(서버쪽X, 서버가 있는 내PC에 저장)
   //String path = "C:\\tmp\\"; (pc에 올리면 웹페이지상에서 볼수가없다 )
   
   // 2-2 서버상에 저장
   String path = request.getRealPath("save"); // 서버상의 save 폴더 실제 경로 찾기
   System.out.println(path);
   // 3. 업로드 할 파일 최대 크기
   int max = 1024*1024*5;   // 5mb
   //4. 인코딩
   String enc = "UTF-8";
   // 5. 덮어씌우기 방지 객체
   DefaultFileRenamePolicy dp = new DefaultFileRenamePolicy();
   
   // MultipartRequest 객체 생성
   MultipartRequest mr = new MultipartRequest(request, path, max, enc, dp);
   
   // 파라미터 받기
   String writer = mr.getParameter("write");
   String sysName = mr.getFilesystemName("upload");   // 업로드 파일 이름
   String orName = mr.getOriginalFileName("upload");   // 파일 원본 이름
   String contentType = mr.getContentType("upload");   // 파일 종류 : 사진, 글....
   
   
   // DB에 저장
   UploadDAO dao = new UploadDAO();
   dao.insertData(writer,sysName);
   
   
   
%>
<body>

   <h2>작성자 : <%=writer%> </h2>
   <h2>업로드 파일명 : <%=sysName%></h2>
   <h2>파일 원본이름 : <%=orName%></h2>
   <h2>컨텐트 타입(파일종류) : <%=contentType%></h2>
	
	<img src="/web/save/<%=sysName%>" width="300px"/>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>Insert title here</title>
	<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
	<br />
	<h1 align="center"> 메인 페이지 </h1>
	
	<% 
	if(session.getAttribute("memId") == null){ // 로그인 안했을때 
		
		// 쿠키가 있는지 검사 
		String id = null, pw = null, auto = null; 
		Cookie[] coos = request.getCookies(); 
		if(coos != null){
			for(Cookie c : coos) {
				// 쿠키가 있다면 쿠키에 저장된 값꺼내 변수에 담기
				if(c.getName().equals("autoId")) id = c.getValue();   
				if(c.getName().equals("autoPw")) pw = c.getValue();
				if(c.getName().equals("autoCh")) auto = c.getValue(); 
			}
		}
		
		// 세개 변수에 값이 들어있을 경우 (쿠키 제대로 생성되서 다 갖고 있다.)
		if(auto != null && id != null && pw != null){
			// 로그인 처리되도록 loginPro.jsp 처리 페이지로 이동시키기 
			response.sendRedirect("loginPro.jsp");
		}
		
		// 위 코드 다 지나치면, 
		// session에 memId속성도 없고, 쿠키도 없으니 로그인 버튼 보여주기
	%>
	<table>
		<tr>
			<td> 로그인을 원하시면 버튼을 누르세요 <br />
				<button onclick="window.location='loginForm.jsp'"> 로그인 </button>
			</td>
		</tr>
		<tr>
			<td> 
				<a href="signupForm.jsp"> 회원 가입 </a>
			</td>
		</tr>
	</table>
	<%}else{ // 로그인 했을때  %>
	<table>
		<tr>
			<td> <%=session.getAttribute("memId") %> 님 환영합니다. <br />
				<button onclick="window.location='mypage.jsp'"> 마이페이지 </button>
				<button onclick="window.location='logout.jsp'"> 로그아웃 </button>
			</td>
		</tr>
	</table>
	<%} %>
	
	
	<br /><br />
	<div align="center">
		<img src="img/ham.jpg" width="400px" />
	</div>

</body>
</html>
<%@page import="web.jsp07.model.MemberDAO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>loginPro</title>
</head>
<%
	// loginForm -> Pro (파라미터 들고옴)
	// main -> pro 쿠키들고 (파라미터 X)

	request.setCharacterEncoding("UTF-8"); // 한글깨짐 방지
	// 넘어온 파라미터 꺼내기 (main에서 바로 왔으면 아래 변수 null)
	String id = request.getParameter("id");
	String pw = request.getParameter("pw");
	String auto = request.getParameter("auto");
	
	// 쿠키를 꺼내서 정보가 나오면 위 변수에 저장 
	Cookie[] coos = request.getCookies(); 
	if(coos != null){
		for(Cookie c : coos){
			if(c.getName().equals("autoId")) id = c.getValue();
			if(c.getName().equals("autoPw")) pw = c.getValue();
			if(c.getName().equals("autoCh")) auto = c.getValue();
			System.out.println(c.getName() + c.getValue());
		}
	}

	MemberDAO dao = new MemberDAO(); 
	boolean result = dao.idPwCheck(id, pw); 
	
	if(result){ // id, pw 일치 : 로그인 상태로 만들기 
		// 자동로그인이면 쿠키도 생성 
		if(auto != null){ // 자동로그인 체크했다.
			Cookie c1 = new Cookie("autoId", id);
			Cookie c2 = new Cookie("autoPw", pw);
			Cookie c3 = new Cookie("autoCh", auto);
			c1.setMaxAge(60*60*24); // 24시간
			c2.setMaxAge(60*60*24); // 24시간
			c3.setMaxAge(60*60*24); // 24시간
			response.addCookie(c1);
			response.addCookie(c2);
			response.addCookie(c3);
		}
	
		session.setAttribute("memId", id); 
		response.sendRedirect("main.jsp"); // 메인으로 이동 
	}else{ %>
		<script>
			alert("아이디 또는 비밀번호가 맞지 않습니다..."); 
			history.go(-1);
		</script>
	<%} %>

<body>

</body>
</html>
<%@page import="web.jsp07.model.MemberDAO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>signupPro</title>
</head>
<%  request.setCharacterEncoding("UTF-8"); // post방식 인코딩 처리  %>
<jsp:useBean id="dto" class="web.jsp07.model.MemberDTO" /> <%-- dto 객체 생성 --%>
<jsp:setProperty property="*" name="dto"/> <%-- 넘어온파라미터 dto에 담기 --%>
<%
	MemberDAO dao = new MemberDAO(); 
	dao.addMember(dto); // 회원 가입 처리 메서드 호출 

	// 메인으로 이동 
	response.sendRedirect("main.jsp"); 
	
%>

<body>

</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>signupForm.jsp</title>
	<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
	<br />
	<h1 align="center"> 회원가입 </h1>
	<form action="signupPro.jsp" method="post">
		<table>
			<tr>
				<td>아이디 *</td>
				<td><input type="text" name="id" /></td>
			</tr>
			<tr>
				<td>비밀번호 *</td>
				<td><input type="password" name="pw" /></td>
			</tr>
			<tr>
				<td>비밀번호 확인 *</td>
				<td><input type="password" name="pwch" /></td>
			</tr>
			<tr>
				<td>이름 *</td>
				<td><input type="text" name="name" /></td>
			</tr>
			<tr>
				<td>성별 </td>
				<td>
					남 <input type="radio" name="gender" value="male" checked />
					여 <input type="radio" name="gender" value="female" />
				</td>
			</tr>
			<tr>
				<td>email</td>
				<td>
					<input type="text" name="email" />
				</td>
			</tr>
			<tr>
				<td colspan="2">
					<input type="submit" value="회원 가입" />
					<input type="reset" value="재작성" />
					<input type="button" value="취소" onclick="window.location='main.jsp'" />
				</td>
			</tr>
		</table>
	</form>


</body>
</html>

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title> loginForm.jsp </title>
  <link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
   <br />
   <form action="loginPro.jsp" method="post">
   <h2 align="center"> 로그인 </h2>
   <table>
      <tr>
         <td>아이디</td>
         <td><input type="text" name="id"></td>
      </tr>
      <tr>
         <td>비밀번호</td>
         <td><input type="password" name="pw"></td>
      </tr>
      <tr>
         <td colspan="2"><input type="checkbox" name="auto" value="1" /> 자동로그인</td>
      </tr>
      <tr>
         <td colspan="2">
         <input type="submit" value="로그인" />
         <input type="button" value="회원가입"  onclick="window.location='signupForm.jsp'"/>
         </td>
      </tr>
   
   </table>
   </form>

</body>
</html>

- 자바

 

package web.jsp07.model;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class MemberDAO {

	   	// 커넥션 메서드 
		private Connection getConnection() throws NamingException, SQLException {
			Context ctx = new InitialContext(); 
			Context env = (Context)ctx.lookup("java:comp/env");
			DataSource ds = (DataSource)env.lookup("jdbc/orcl"); 
			return ds.getConnection(); 
		}
		//회원가입 처리
		public void addMember (MemberDTO dto) {
			Connection conn = null; 
			PreparedStatement pstmt = null; 
			ResultSet rs = null; 
			
			try {
				conn = getConnection(); // Naming/SQLException 발생가능 
				String sql = "insert into member values(?,?,?,?,?,sysdate)";
				// 커넥션으로 준비한 쿼리문 주고 실행기능 갖는 pstmt 리턴받기 (쿼리문 준비)
				pstmt = conn.prepareStatement(sql);
				pstmt.setString(1, dto.getId());
				pstmt.setString(2, dto.getPw());
				pstmt.setString(3, dto.getName());
				pstmt.setString(4, dto.getGender());
				pstmt.setString(5, dto.getEmail());
			
			
				int result  = pstmt.executeUpdate(); 
				System.out.println("insert result : " + result);
			
			
				
			}catch(Exception e) { // Naming/SQLException 발생했을때 둘다 받아줄 부모로 처리 
				e.printStackTrace();
			}finally {
				
				if(pstmt != null) try { pstmt.close(); } catch(SQLException e) { e.printStackTrace(); }
				if(conn != null) try { conn.close(); } catch(SQLException e) { e.printStackTrace(); }
			}
			
		}
			
		// 로그인 : id, pw 맞는지 체크해서 결과 돌려주는 메서드
		//			id,pw 맞으면 true, 둘중하나라도 틀리거나 없으면 false
		public boolean idPwCheck(String id, String pw) {
			System.out.println("idPwCheck 호출!");
			boolean result = false; // 최종 결과 리턴해줄 변수 미리 선언
			Connection conn = null; 
			PreparedStatement pstmt = null; 
			ResultSet rs = null; 
			
			try {
				conn = getConnection(); // Naming/SQLException 발생가능 
				String sql = "select * from member where id =? and pw =?";
				pstmt = conn.prepareStatement(sql);
				pstmt.setString(1, id);
				pstmt.setString(2, pw);
				
				rs = pstmt.executeQuery(); 
				// rs에 돌려받은 결과에 따라 result 변수값 변경 
				if(rs.next()) {
					System.out.println("rs.next() -> true");
					result = true;
				}
				
			}catch(Exception e) { // Naming/SQLException 발생했을때 둘다 받아줄 부모로 처리 
				e.printStackTrace();
			}finally {
				if(rs != null) try { rs.close(); } catch(SQLException e) { e.printStackTrace(); }
				if(pstmt != null) try { pstmt.close(); } catch(SQLException e) { e.printStackTrace(); }
				if(conn != null) try { conn.close(); } catch(SQLException e) { e.printStackTrace(); }
			}
			return result; // result변수에 들어있는 결과 리턴 
		}
	}

- 자바

package web.jsp07.model;

import java.sql.Timestamp;

public class MemberDTO {
				private String id;
				private String pw;
				private String name;
				private String gender;
				private String email;
				private Timestamp reg;
			
				
				public String getId() {
					return id;
				}
				public void setId(String id) {
					this.id = id;
				}
				public String getPw() {
					return pw;
				}
				public void setPw(String pw) {
					this.pw = pw;
				}
				public String getName() {
					return name;
				}
				public void setName(String name) {
					this.name = name;
				}
				public String getGender() {
					return gender;
				}
				public void setGender(String gender) {
					this.gender = gender;
				}
				public String getEmail() {
					return email;
				}
				public void setEmail(String email) {
					this.email = email;
				}
				public Timestamp getReg() {
					return reg;
				}
				public void setReg(Timestamp reg) {
					this.reg = reg;
				}
			
}

 

create table member( --테이블 만들기
    id varchar2(50) primary key,
    pw varchar2(50) not null,
    name varchar2(20) not null,
    gender varchar2(20),
    email varchar2(50),
    reg date default sysdate
);
desc member;  -- 테이블(맴버)참조
commit; -- 커밋
select * from member;  -- 출력용

진행 순서 
1) 회원가입에 필요한 항목을 DB 테이블 만들기

2) Member DTO로 변수와 get/set메서드 만들기  

 3) Member DAO 만들기 

 

1 - DB와 연결하는 메서드

2 - 회원가입에 필요한 메서드
받은값들을 연결된 DB에 차례대로 넣어주는 메서드

3 - 로그인에 필요한 메서드 생성 
Id 와 PW가 db값과 같은지 체크하는 메서드


 4) loginForm 만들기 

 

1 - 폼, 액션태그와 input에 변수들 넣어주기
2 - 폰트, 이미지등 CSS스타일 관리 
3 - 로그인, 회원가입 관리 
<form action="태그.jsp" method="post"> 또는 onclick을 이용하여 페이지 이동 만들기
로그인 -> login Pro로 이동
회원가입 -> signupForm 으로이동

 

 

 


   4) -  1 <회원가입 폼 (signup Form)>      

    4)에서 회원가입을 눌렀을때 실행 

 

1 - loginForm 과 유사함
2  -<form action="태그.jsp" method="post"> 
또는 onclick을 이용하여 페이지 이동 만들기
회원가입 -> signup Pro로 이동
취소버튼 -> main 으로이동(로그아웃상태)

 

 

 


     4) -  1  - 1  회원가입 처리폼 (Signup Pro)

 

 1-  DTO 객체 생성후 넘어온 파리미터를 DTO에 담아줌
 2-  DAO 객체 생성후 회원가입 메서드 작동
 3-  main 으로 이동(로그인상태)

 

 

 


4) - 2 로그인 처리폼(login Pro)

 

login Pro에 오자마자 파라미터꺼냄 
쿠키있으면 변수생성     

DAO 객체 생성하여 3) -1 에 로그인에 필요한 메서드 실행

 

 

 <1- 메서드가 실행되어 ip/ pw가 일치하는 경우>

                      ●a -  auto가 null이 아니다! (자동로그인체크)
                                -> 쿠키 객체 생성하고 유효시간과 쿠키값주고 세션 속성값 주고
                                       main으로 보냄(로그인 상태)
 

                      ●b -  auto 가 null 이면
                                     -> 세션 속성값만 주고 바로 
                                           main으로 보냄(로그인 상태) 


** 세션 속성값 -> 난 로그인처리를 했어!

 <2 - 메서드가 실행되었는데 ip/ pw가 일치하는 않는 경우>
 경고창 띄우고 
한칸 뒤로 (history.go(-1) 실행

 


   5 메인 폼 (Main Form)   만들기 

 

 <1 -   세션 속성값이 없을때> (login Pro를 통해 온게 아님-> 로그인으로 온게아님) 
             
              a  -         쿠키가 있다 ->  login pro로 보내줌 (자동로그인 체크한 경우이니 로그인 처리가 되도록)
                             login pro에서 
                                       유효시간, 쿠키값, 세션 속성값 주면서
                                       main으로 보냄
 
              b  -         쿠키가 없다 -> login Form으로 보냄 



< 2 -   세션 속성값이 있다> (login pro에서 들어온거임)(자동로그인x 로그인)

 

+ Recent posts