안녕하세요
작년에 대학원 기술서 쓰고 지원서 제출해서 올해 봄학기 부터 대학원을 다니기 시작했는데요. 생각해보니 대학원은 전문적 기술이나 교육을 받는것 같습니다. 봄 학기 마쳤고 현재 가을 학기 다니고 있습니다.
논문쓰는 기술을 배운다고 하는데 그것을 통해서 공부를 하고 교육이 이루어지는 것 같습니다.


https://www.comodossl.co.kr/certificate/ssl-installation-guides/Apache-csr-crt.aspx

 

Apache : CSR 생성 및 SSL 인증서 적용 - HanbiroSSL

Apache : CSR 생성 및 SSL 인증서 적용 본 가이드는 인증서 파일 설정시 참고 예제이며, 고객사 서버에 SSL 설치/적용 성공을 보증하지 않습니다 설정 과정에서 발생하는 다양한 문제에 대한 해결은 보증 대상이 아닙니다 (인증서 자체 오류시 무제한 재발급) 웹서버 자체 설정/작동 관련 사항은 웹서버 매뉴얼 또는 공급사 기술지원을 이용하시기 바랍니다 Apache 설치방법 보기 Apache 웹서버에 SSL를 적용하기 위해 아래 두 항목이 웹서버에 설치되어 있어야 합니다. - Openssl 암호화 라이브러리 - Mod_ssl 모...

www.comodossl.co.kr

Apache : CSR 생성 및 SSL 인증서 적용


본 가이드는 인증서 파일 설정시 참고 예제이며, 고객사 서버에 SSL 설치/적용 성공을 보증하지 않습니다

설정 과정에서 발생하는 다양한 문제에 대한 해결은 보증 대상이 아닙니다 (인증서 자체 오류시 무제한 재발급)

웹서버 자체 설정/작동 관련 사항은 웹서버 매뉴얼 또는 공급사 기술지원을 이용하시기 바랍니다

Apache 설치방법 보기

Apache 웹서버에 SSL를 적용하기 위해 아래 두 항목이 웹서버에 설치되어 있어야 합니다.

- Openssl 암호화 라이브러리

- Mod_ssl 모듈

위 두 항목이 웹서버에 설치되어 있다면 개인키를 생성하고 생성된 개인키를 바탕으로 CSR 파일을 생성합니다.

생성된 CSR 파일을 한비로에 접수하여 정식 인증서를 발급받습니다.

발급된 인증서를 웹서버에 설치하게 되면 SSL 설정을 완료하게 됩니다.

위 일련의 진행사항은 아래와 같은 절차를 따르게 됩니다.

1. openssl 라이브러리 설치상태 확인

2. mod_ssl 모듈 설치상태 확인

3. 개인키 생성

4. CSR 생성

5. 한비로에 접수

6. 정식 인증서 발급

7. SSL 설정

 

1. Openssl 라이브러리 설치상태 확인

먼저 SSL를 설치하고자 하는 웹서버에 openssl 라이브러리 설치상태를 find 명령어를 활용하여 아래와 같이 확인합니다.

 

위와 같은 값을 보인다면 openssl 라이브러리 모듈은 rpm으로 설치된것입니다.

만약에 경로가 /usr/local 아래 있다면 모듈은 소스로 설치된 것입니다.

rpm으로 설치된 것이라면 rpm -qa 명령어를 사용하여 openssl-devel 설치여부도 함께 점검합니다. 버전에 따라 라이브러리 버전이 아래보이는 값과 차이가 있을수 있습니다.

 

Openssl 은 암호화 처리를 위한 독립 모듈로 최신버젼으로 설치하는 것을 권장합니다.

2. Mod-ssl Openssl 라이브러리 설치상태 확인

Openssl 과 마찬가지로 웹서버의 mod_ssl 설치여부를 점검합니다.

Apache 웹서버는 두가지 방식으로 모듈설치를 지원하고 있습니다. 정적과 동적인 방식으로 정적으로 설치된 경우는 아파치의 재설치까지 요구되며 동적인 경우는 손쉽게 모듈 설치가 가능합니다.

Apache 가 /usr/local 아래 설치된 것을 기준으로 아래와 같은 방식으로 확인합니다.

정적으로 설치된 mod_ssl 모듈확인

 

동적으로 설치된 mod_ssl 모듈확인

 

웹서버에 설치된 모듈중 mod_so.c 를 먼저 확인후 동적으로 설치된 모듈중 mod_ssl.so 를 확인합니다.

동적으로 설치된 경우 apache 설치 디렉토리의 module 이나 libexec 디렉토리내에 mod_ssl.so 의 존재여부를 확인합니다.

3. 개인키 생성

Openssl 명령어를 이용하여 웹서버의 RSA키( 2048비트 암호화 )를 생성합니다. ( sslhanbiro.key 는 임의로 지정된 키값입니다. 원하는 이름으로 키를 생성합니다. )

패스워드를 지정하게 됩니다. 이때 입력된 패스워드는 차후 여러차례 사용되므로 본인만이 알 수 있는 패스워드로 지정해 주시는 것이 좋습니다.

 

윈도우 아파치 이용하실 경우 '-des3' 구문은 제외하시기 바랍니다.

이때 생성되는 개인키는 반드시 백업을 받아놓고 사용하는 것이 좋습니다.

생성된 키는 아래와 같이 확인이 가능합니다. 패스워드를 확인 하는데 이때는 키생성시 입력한 패스워드를 입력합니다.

개인키 생성까지 완료되면 이제 CSR 생성을 하게됩니다.

 

4. 인증요청서( CSR ) 생성

*발급이 완료된 인증서는 재발급 또는 변경이 불가하므로 CSR 생성시 절대 주의 바랍니다.

☞ CSR ( Certificate Signing Request ) 이란?

SSL 서버를 운영하는 회사의 정보를 암호화하여 인증기관으로 보내 인증서를 발급받게 하는 일종의 신청서입니다.

CSR은 ASCII 텍스트 화일로 생성됩니다.

CSR을 생성할 때 서버의 식별명을 입력하게 됩니다. 식별명은 각 서버를 공유하게 나타내는 이름으로 다음과 같은 정보를 포함합니다.

Country Name ( 국가코드) [] : KR

State or Province Name ( 지역 ) [] : Seoul

Locality Name ( 시/군/구 ) [] : Seocho

Organization Name ( 회사명 ) [] : Hanbiro Inc

Organizational Unit Name ( 부서명 ) [] : Linux Team

Common Name ( 서비스도메인명 ) [] : www.hanbiro.com

Email Address [] : hanbiro@hanbiro.com

☞ CSR 항목에 대한 설명

Country Name : 이것은 두 자로 된 ISO 형식의 국가 코드입니다.

State or Province Name : 시 이름을 입력해야 하며 약어를 사용할 수 없습니다.

Locality Name : 이 필드는 대부분의 경우 생략이 가능하며 업체가 위치한 곳를 나타냅니다.

Organization : 사업자 등록증에 있는 회사명과 일치되는 영문회사명을 입력하시면 됩니다.

Organization Unit : "리눅스 관리팀", "윈도우 관리팀" 등과 같이 업체의 부서를 입력할 수 있습니다.

Common Name : 인증받을 도메인주소를 입력하시면 됩니다.

이 정보로 웹 사이트를 식별하므로 호스트 이름을 변경할 경우 다른 디지털 ID를 요청해야 합니다.

호스트에 연결하는 클라이언트 브라우저가 디지털 ID의 이름과 URL이 일치하는지를 확인합니다.

☞ CSR 항목 입력시 주의사항

* Common Name 에는 인증서를 설치할 사이트의 도메인의 이름을 정확하게 입력하셔야 합니다.

* Common Name 에는 IP 주소, 포트번호, 경로명, http:// 나 https:// 등은 포함할 수 없습니다.

* CSR 항목에는 < > ~ ! @ # $ % ^ * / \ ( ) ? 등의 특수 68 문자를 넣을 수 없습니다.

* CSR 생성후 서버에 개인키 (Private Key) 가 생성됩니다. 개인키를 삭제하거나 분실할 경우 인증서를 발급받아도 설치가 불가합니다. 따라서 꼭 개인키를 백업받아 두셔야 합니다.

* 정보입력과정 마지막에 나오는 A challenge password 와 An optional company name 두 항목은 입력하지 마시고 Enter 만 누르고 넘어가야 합니다. 두 정보가 입력될 경우 잘못된 CSR 생성될 수 있습니다.

↘ 위 주의사항을 유의하여 아래와 같은 절차로 CSR 생성을 진행합니다.

(* 윈도우+아파치의 경우 -config "openssl.cnf 절대 경로" 입력 하시기 바랍니다.)

 

↘ 생성된 CSR 정보는 아래처럼 확인이 가능합니다.

 

5. 한비로에 접수--->6. 정식 인증서 발급

생성된 CSR 을 출력하면 아래와 같은 base64 형식의 문서를 볼 수 있습니다.

 

이문서의 첫 줄 -----BEGIN … 부터 마지막 줄 -----END … 까지 복사하여 지정된 SSL 접수페이지에 복사하여 붙여 넣은 뒤 입력정보와 함께 전송하면 접수가 완료됩니다.

7. 인증서 설치

접수한 CSR 파일이 정상적으로 생성되었다면 별다른 문제없이 인증서를 발급 받을 수 있습니다.

인증서 파일은 신청시 기록한 Email 주소를 통해 인증서를 첨부파일로 수신하게 됩니다.

① 인증서 서버에 복사

도메인.crt와 Chain 폴더를 보실 수 있습니다.

여기에서 필요한 파일은 도메인.crt 와 Chain 폴더 안에 있는 AddTrustExternalCARoot.crt, rsa-dv.chain-bundle.pem 이 두 파일 입니다.

② 웹서버 환경설정

아파치가 설치된 디렉토리로 이동하여 conf 디렉토리내의 httpd.conf 파일의 복사본을 만들어 둡니다.

웹서버 설정의 기본이 되는 파일로 만일의 경우를 대비하여 백업본을 유지하는 것이 좋습니다.

백업이 완료되면 vi 편집기를 이용하여 httpd.conf 내용중 아래사항을 설정하신 정보에 맞게끔 수정합니다.

httpd.conf 예문

 

1. <VirtualHost 127.0.0.1:443> 127.0.0.1 를 사용하는 장비의 아이피로 변경

- 443 : SSL 통신포트번호 입니다. 일반적으로 웹서버는 80 포트를 사용합니다.

2. SSLEngine 스위치 off 를 on 으로 변경해 줍니다.

3. SSLCertificateFile / SSLCertificateKeyFile 에는 인증서의 설치경로와 개인키 파일의 경로를 적어 줍니다.

③ root 인증서 경로설정

SSLCACertificateFile 에는 root 인증서 위치를 알려주는 것으로 유저의 브라우저에 신뢰받는 CA리스트가 없을 경우를 위해 경로를 반드시 지정해 주어야 합니다.

④ 웹서버 재실행

 

설정파일의 정상적인 수정여부를 점검하기 위한 체크 ./httpd -t

수정된 사항의 적용을 위해 아파치 데몬정지 ./apachectl stop

아파치 데몬 활성화 ./apachectl startssl ( 아파치 데몬 활성화는 ./apachectl start 로 가능합니다. 여기에 ssl 를 붙여줌으로서 ssl 를 사용하게 됩니다. )

초기 개인키 생성시 입력했던 패스워드를 기억하시고 계실겁니다. SSL 실행을 위해 패스워드를 물어보는데 이때 개인키 생성시 입력했던 패스워드를 입력하시면 SSL 웹데몬이 활성화 됩니다.

⑤ 웹서버 포트점검

아래와 같이 활성화된 데몬의 포트를 점검해 봅니다.

 

⑥ 웹서비스 동작상태 점검

인터넷 주소창에 https://사용도메인 와 입력후 해당 페이지의 정상적인 동작 여부를 점검합니다.

SSL 설정한 사이트에 대한 정상적인 서비스 상태 점검

 

페이지 하단을 보시면 열쇠 아이콘이 보이게 됩니다. 아이콘을 클릭하게 되면 위와 같이 인증서 정보를 확인하실수 있습니다.

인터넷 익스플로러 주요 단축키로 편리하게 이용하는 방법

 

환경: Internet Explorer 11

 

컴퓨터에서 제일 많이 사용하는 프로그램 중 하나가 브라우저 입니다. 이렇게 자주 사용하는 프로그램들은 단축키를 외워 두는 것이 좋습니다. 단축키를 이용해서 프로그램을 다루게 되면 작업 속도를 올릴 수 있을 뿐만 아니라 마우스 보다 편합니다. 오늘은 주로 사용하는 단축키를 추려서 알려 드리겠습니다. 많이 소개해 봐야 나중에 기억할 수도 없습니다.

 

▼ 아래 소개하는 단축키들은 브라우저 공용입니다. 크롬과 파이어폭스에도 동일하게 적용될 수 있으므로 익혀 두면 여러모로 편합니다. 먼저 Alt + D 단축키는 주소 표시줄로 커서를 이동시킵니다. URL 또한 반전시키기 때문에 바로 자신이 원하는 검색어서 주소를 입력할 수 있습니다.

존재하지 않는 이미지입니다.

 

Alt + HOME 단축키는 홈으로 설정한 웹 페이지로 이동하게 해 줍니다. 브라우저 오른쪽 상단에 집 모양의 아이콘인 홈을 누른 것과 같습니다.

존재하지 않는 이미지입니다.

 

Ctrl + W 는 현재 활성화 되어 있는 탭을 닫아 줍니다. 여러 개의 탭들이 떠 있을 때 일일이 마우스로 탭의 X 버튼을 누르지 않아도 됩니다.

존재하지 않는 이미지입니다.

 

Ctrl + Tab 단축키는 Alt + Tab 과 달리 브라우저의 탭을 전환합니다. 계속해서 오른쪽으로 이동하다가 끝에 이르면 다시 처음으로 되돌아 옵니다.

존재하지 않는 이미지입니다.

 

Ctrl + N 단축키는 현재 활성화된 탭을 복사해서 새로운 창으로 띄웁니다.

존재하지 않는 이미지입니다.

 

▼ 이상으로 사용 빈도가 높은 단축키들을 알아 봤습니다. 다음은 많이 사용하지는 않지만 알아 두면 편한 단축키들입니다.

 

l Ctrl + T : 새 창 열기

l Ctrl + K : 현재 탭을 새 탭으로 복사해서 생성, Ctrl + N 은 별도의 창으로 생성

l Ctrl + 1,2,3.. : 탭 번호 이동, 제일 앞에 탭부터 1,2,3 으로 부여

l Shift + 클릭 : 링크 주소를 탭이 아닌 새 창을 띄워서 보여줌

l Alt + 이동키 : 브라우저 앞으로, 뒤로 이동과 같음

l Ctrl + F : F3 와 같고 웹 페이지 내에 텍스트 검색 창 띄움

l Ctrl + 플러스 : 창을 확대

l Ctrl + 마이너스 : 창을 축소

l F11 : 전체 화면으로 확대

l Ctrl + J : 다운로드 관리자 창 열기

l Ctrl + HOME : 스크롤된 페이지에서 제일 위로 올라감

l Ctrl + END : 스크롤된 페이지에서 제일 아래로 내려감

[ 스위치 모드 변경하기 ]

 

① Switch>enable ☞ 유저모드(User Mode) ② Switch#config terminal ☞ 프리빌리지 모드(관리자모드)

 

③ Switch(config)#interface fastethernet 0/1 ☞ 구성모드(configuration mode)

 

④ Switch(config-if)# ☞ 인터페이스 구성모드

 

⑤ Switch(config-if)#exit -> 빠져나가기

 

⑥ Switch(config)#exit ⑦ Switch# exit ⑧ Switch> enable

 

⑨ Switch# show interface status -> 포트별 상태를 알아보기 ( 명령어는 반드시 관리자모드에서 입력해야한다.)

 

⑩ Switch# show ? -> show 다음 입력할 수 있는 명령어 모음 표시

 

⑪ Switch# show interface ?

 

⑫ Switch# show interfaces -> 각 포트의 설정보기 ⑬ Switch# show interface -> 각 포트의 설정보기

 

[ 포트설정 바꾸기 ]

 

① Switch>enable ② Switch# configure terminal

 

③ Switch(config)# interface fastethernet 0/1

 

④ Switch(config-if)# speed 100 -> 속도를 100M로 설정

 

⑤ Switch(config-if)# duplex full -> 전송방식을 전이중으로 설정

 

⑥ Switch(config-if)# exit -> 구성모드로 가기 ⑦ Switch(config)# exit -> 관리자 모드로 가기

 

⑧ Switch# show interface status

 

⑨ Switch# show interface fastethernet 0/1 (speed {auto | 100 | 10 } duplex {auto | half | full} )

 

1번 포트를 속도(대역폭)은 100Mbps, 통신방식은 Full-duplex ,2번 포트를 속도(대역폭)은 10Mbps, 통신방식은 half-duplex로 설정하시오.

 

[ hostname 설정하기]

 

① Switch#config terminal ② Switch(config)# hostname atom 또는 hostname 홍길동

 

홍길동# //atom은 이름이므로 다른 이름을 마음대로 사용할수 있다.

 

 

[ secret 암호설정하기]

 

① Switch# config terminal

 

② Switch(config)# enable secret atom123 //암호는 자유롭게 지정할 수 있다.(암호는 구성모드에서 지정한다)

 

③ Switch(config)# exit ④ Switch# exit

 

Switch> enable

 

Password: ******

 

Switch# // 이제는 암호를 넣어야 관리자모드로 들어갈 수 있다.

 

[ 스위치에 IP주소 설정하기 ]

 

① Switch> enable ② Switch# config terminal

 

③ Switch(config)# interface vlan 1 ④ Swtitch(config-if)# ip address 192.168.100.1 255.255.255.0

 

⑤ Switch(config-if)# exit ⑥ Switch(config)# exit

 

⑦ Switch# show interface vlan1 또는 vlan 1

 

[ 기본 게이트웨이 설정하기 ]

 

① Switch# config terminal ② Switch(config)# ip default-gateway 192.168.0.1

 

③ Switch(config)# exit ④ Switch# show running-config // 현재 스위치에 설정된 모든 내용 보여주기

 

[ 암호 삭제하기 ] ① Switch(config)# no enable secret password 지정한 암호

 

[ 라우터 명령어 집합 ]

 

Router>enable (사용자모드에서 관리자모드로 전환하기)

 

[현재 사용하고 있는 구성파일 보기-RAM]

 

Router# show running-config 또는 write terminal

 

[NVRAM에 저장된 구성파일 보기]

 

Router# show startup-config

 

[현재 구성을 NVRAM에 저장하기]

 

Router# write memory 또는

 

copy running-config startup config

 

[ 플래시 메모리 정보보기 ]

 

Router# show flash

 

[ 라우터의 현재정보 보기 ]

 

Router# show version

 

[ 인터페이스 정보보기 ]

 

Router# show interface

 

[ 이더넷 인터페이스 0번 보기 ]

 

Router# show interface ethernet 0

 

[ 라우터 CPU 사용률 보기 ]

 

Router# show processes cpu

 

[ 라우터 메모리 상태 보기 ]

 

Router# show memory

 

[ 구성모드로 들어가기 ]

 

Router# configure terminal

 

Router(config)# CTRL + P -> 명령어 히스토리 단축키

 

[ 라우터의 이름과 enable secret , enble password 설정하기 ]

 

Router(config)# hostname CISCO-2500

 

CISCO-2500(config)# enable secret cisco

 

CISCO-2500(config)# enable password seoul

 

CISCO-2500(config)# exit

 

CISCO-2500# exit

 

CISCO-2500> enable

 

Password: secret 암호가 설정되어있으면 secret를 넣는다. 암호는 대소문자 구분합니다.

 

CISCO-2500# show running-config (현재 설정된 구성정보보기)

 

secret 암호와 패스워드 암호가 보인다.

 

[ 텔넷 접속 패스워드 설정하기 ]

 

CISCO-2500> enable

 

Router#> configure terminal

 

Router(config)# line vty 0 4

 

Router(config-line)#login ( 라인구성모드 )

 

Router(config-line)#password korea

 

Router(config-line)# exit

 

Router(config)#

 

[ 인터페이스 구성모드 들어가서 이더넷에 IP주소 할당하기 ]

 

Router# configure terminal

 

Router(config)# interface ethernet 0

 

Router(config-if)# no shutdown ( 인터페이스 살리기 )

 

Router(config-if)#ip address 211.23.156.30 255.255.255.224

 

Router(config-if)# exit

 

Router(config)# exit

 

[ 시리얼 인터페이스 설정하기 ]

 

Router# configure terminal ( 구성모드로 들어가기 )

 

Router(config)# interface serial 0

 

Router(config-if)# no shutdown

 

Router(config-if)# ip address 10.123.51.41 255.255.255.252

 

Router(config-if)# exit

 

Router(config)# exit

 

Router# show running-config (현재 구성설정보기 )

 

ethernet 0 에 할당된 IP 주소를 확인한다. 텔넷에 할당된 암호를 확인한다.

 

Router# show interface ( 현재 인터페이스 보기 )

 

이더넷0가 up 으로 설정되어 있는지 확인한다.

 

[ 라우팅 프로토콜 설정하기 ]

 

Router> enable

 

Router# config terminal

 

Router(config)# router igrp 100

 

Router(conifg-router)# network 211.23.156.0

 

Router(conifg-router)# network 10.0.0.0

 

Router(conifg-router)# Ctrl+Z(exit) ( 프리빌리지모드로 나가기)

 

Router# show running-config ( 구성파일 보기 )

 

[RAM의 구성파일을 NVRAM에 저장하기]

 

Router# copy running-config startup-config

 

[ 플래시 메모리 내용보기 ]

 

Router# show flash

 

[IOS 이미지 파일을 TFTP 서버로 백업하기]

 

Router# copy flash tftp

 

[ TFTP서버에서 플래시메모리 IOS 파일 복원하기 ]

 

Router# copy tftp flash

 

[ 라우터의 구성파일 백업하기 ]

 

Router# copy startup-config tftp

 

[라우터의 구성파일 복원하기]

 

Router# copy tftp startup-config

 

[ 라우팅 테이블 보기 ]

 

Router# show ip route

 

[ 스태틱 라우팅 구현하기 ]

 

Router# configure terminal

 

Router(config)# ip route 210.162.65.0 255.255.255.0 203.155.10.2 1

 

Router(config)# exit

 

Router# show ip route

 

[ 디폴트 라우팅 설정하기 ]

 

Router>enable

 

Router# configure terminal

 

Router(config)# interface ethernet 0

 

Router(config-if)# ip address 211.23.123.1 255.255.255.0

 

Router(config-if)# exit

 

Router(config)# interface serial 0

 

Router(config-if)# ip address 61.105.112.41 255.255.255.252

 

Router(config-if)# exit

 

Router(config)# ip route 0.0.0.0 0.0.0.0 61.105.112.42

 

Router(config)# exit

 

Router# write

 

Router#

 

 

이더넷 스위칭과 IP 라우팅 기능을 겸비한 Layer 3 스위치의 주요 특징은 다음과 같습니다.

 

• QoS (Quality of Service)

 

일반적인 네트워크 환경에서는 트래픽이 폭주할 경우 사용자의 데이터는 자동적으로 유실(drop)됩니다. 그러나 QoS를 지원하는 스위치는 IEEE 802.1p CoS 표준안에 기반하여 트랙픽을 여러 개의 등급으로 나누고, 각 등급의 처리 순서를 다시 정립합니다(reprioritize). QoS는 중요한 데이터의 우선 순위를 정해 놓음으로써 데이터이 유실을 막고, 패킷마다 차등화 된 대역폭을 제공하여 전송 지연을 방지합니다.

 

• 멀티캐스트 통신

 

이 스위치는 IGMP Snooping 기능과 IGMP Querier기능을 제공하기 때문에 멀티캐스트 통신이 가능한 장비입니다. 멀티캐스트 통신은 필요로 하는 호스트들에게만 패킷을 전송하기 때문에 불필요한 패킷으로 과부하 현상이 일어나는 것을 막을 수 있습니다.

 

• SNMP (Simple Network Management Protocol)/RMON (Remote Monitoring)

 

SNMP 기능이 탑재된 스위치는 원격에서 스위치 상태를 확인하고 관리할 수 있습니다. 스위치는 SNMP 버전 1과 2과 4가지 그룹의 RMON을 지원, 관리자가 원하는 때에 원하는 통계 자료를 볼 수 있습니다.

 

• IP 라우팅

 

스위치는 일반적으로 OSI 계층 가운데 Layer 2에 해당하는 동작을 합니다. 그러나, V스Layer 3 스위치는 라우터가 가지고 있는 기능인 IP 라우팅을 실현, 라우터를 별도로 설치할 필요가 없기 때문에 장비 추가에 따른 비용을 줄일 수 있습니다.

 

• ARP-Alias

 

스위치는 장비에 등록되어 있지 않은 IP 주소에 대해 ARP 응답을 해줄 수 있습니다. 이러한 기능은 서로 통신이 불가능한 노드들 간의 통신을 해소해줄 수 있습니다.

 

 

 

• 네트워크 기반의 IP 패킷 Forwarding

 

새로워진 스위치는 IP 패킷을 Forwarding 하는 방법을 네트워크 단위로 기억할 수 있게 함으로써 스위칭 칩에서 기억할 수 있는 엔트리를 확대시켰습니다. 네트워크 기반의 IP 패킷 Forwarding 방법은 최대 13개까지 기억할 수 있습니다.

 

• DHCP Server 및 Relay 기능

 

스위치는 클라이언트에게 자동으로 IP 주소를 부여하는 DHCP 기능을 지원하여 한정된 네트워크 자원을 보다 효율적으로 이용하도록 합니다. 특히 DHCP 서버는 중앙에서 일괄적으로 IP 주소를 관리하여 네트워크 관리 비용을 절감시켜 줍니다.

 

• VLAN(Virtual Local Area Network)

 

VLAN이란 네트워크 관리자가 하나의 네트워크를 논리적으로 분리하여 만든 가상 LAN을 말합니다. VLAN은 물리적으로는 같은 네트워크 상에 있지만 사용자의 설정에 따라 같은 네트워크로 구성된 영역에서만 패킷을 주고 받을 수 있기 때문에 대역폭을 경제적으로 활용할 수 있을 뿐만 아니라 보안 효과가 뛰어납니다. 스위치는 하나의 시스템 당 최대 256개의 VLAN을 구성할 수 있습니다.

 

• Proxy-ARP

 

Proxy-ARP는 쉽게 말해 다른 장비의 ARP 응답을 대신 해 줄 수 있는 기능으로 서로 다른 서브네트워크간의 통신을 가능하게 해 줍니다.

 

• 패킷 필터링

 

IP 패킷 필터링은 특정 장비나 사용자만이 네트워크에 접속할 수 있도록 네트워크 사용을 제한할 수 있는 기능입니다. 스위치는 이 기능을 이용하여 사용자는 불필요한 정보를 차단하고 특정 데이터가 유출되는 것을 방지하는 것은 물론 신원이 확인되지 않은 사용자를 차단함으로써 네트워크 보안을 강화할 수 있습니다. 한편, 다른 Source IP 주소를 가지고 외부로 나가는 패킷을 차단하는 Martian-filter 기능과 아파트나 특정한 지역에 LAN 서비스로 인터넷 통신이 제공되는 경우, 사용자들의 정보를 보장하는 NetBIOS 필터링도 제공합니다.

 

 

 

• 스택킹 (Stacking)

 

스위치 그룹에서 master로 지정된 스위치가 하나의 IP 주소를 가지고 나머지 스위치(slave 스위치)를 설정 및 관리, 모니터링 할 수 있는 기능입니다. 하나의 IP 주소로 여러 대의 스위치를 관리할 수 있기 때문에 IP 자원을 절약할 수 있습니다.

 

• Link aggregation

 

스위치는 여러 개의 물리적인 인터페이스를 하나의 논리적인 포트로(aggregate port) 통합하는 포트 트렁크 기능을 지원합니다. 포트 트렁크는 동일한 속도, 동일한 duplex 모드, 동일한 VLAN ID를 기준으로 인터페이스를 통합합니다. 스위치는 트래픽을 줄이고 장애 복구 기능을 향상 시키기 위해 IEEE 802.3ad 표준안에 따라 최대 8개의 포트를 포괄하는 통합 포트를 6개까지 설정할 수 있습니다.

 

• LACP(Link Aggregation Control Protocol)

 

스위치는 IEEE 802.3ad 표준을 기반하는 LACP를 지원하는데, 이는 LACP를 지원하는 장비간에 더 많은 전체 대역을 할당할 수 있도록 장비간 다중 연결 결합을 허용합니다.

 

• 대역폭 설정(Rate-limit)

 

스위치는 모든 포트에 대해 차등화된 대역폭을 제공합니다. 사용자의 요구에 따라 차등화된 대역폭을 제공함으로써 ISP 사업자는 차등화된 요금을 책정할 수 있을 뿐만 아니라 보다 효율적이고 경제적인 회선 관리가 가능합니다.

 

• Flood Guard 설정

 

Rate Limit는 포트 대역폭을 설정하여 패킷의 양을 조절하는 것과는 달리 1초 동안 수용할 수 있는 패킷 개수를 제한하여 패킷을 조절하는 Flood Guard 기능을 제공합니다. 

 

• STP(Spanning Tree Protocol)

 

STP란 네트워크 상에서 루프가 계속해서 발생하는 것을 방지하기 위한 네트워크 관리 프로토콜입니다. 루프를 방지하기 때문에 트래픽 전송 속도를 유지하도록 도와줍니다. 스위치는 이러한 STP 기능을 가지고 있습니다.

 

• PVST(Per VLAN Spanning Tree)

 

스위치는 VLAN마다 STP이 독립적으로 동작하는 PVST(Per VLAN Spanning Tree)를 지원합니다. PVST(Per VLAN Spanning Tree)는 VLAN 마다 STP가 하나씩 돌기 때문에 하나의 VLAN에서 루프가 발생하여 전체 네트워크가 다운되는 현상을 막을 수 있습니다.

 

• RSTP(Rapid Spanning Tree Protocol) (802.1w)

 

스위치는 IEEE 802.1W 표준안에 따른 RSTP(Rapid Spanning Tree Protocol)를 지원하여 메트로 이더넷의 RING 환경이나 기존 P-to-P 환경에서 안정적이고 융통성있는 망구성이 가능합니다. RSTP는 소규모 스위치 네트워크에서 STP Reconvergency 시간을 혁신적으로 감소시키기 위한 목적으로 개발된 것으로, Redundant link를 갖는 Layer 2 스위치에서 Fail over 시간을 획기적으로 단축시킵니다.

 

• 시스템 관리

 

ⅰ) Web 기반 관리

웹을 기반으로 언제 어디서나 사용 가능한 GUI 모듈입니다. 스위치를 이용하여 관리자는 스위치를 설정하고 유지, 관리할 수 있습니다. 

 

이 모듈을 사용하여 사용자는 네트워크에 연결된 시스템과 포트의 동작 상태를 확인하는 것은 물론 네트워크 구성도도 출력할 수 있습니다.

 

ⅱ) CLI 기반 DSH

사용자는 명령문 형식으로 구성된 DSH을 이용하여 하나의 스위치나 스위치 그룹 전체를 설정하고 모니터링할 수 있습니다. 콘솔 터미널 프로그램이 설치된 PC와 스위치 콘솔을 연결하거나 텔넷 서비스를 이용하여 DSH을 이용할 수 있습니다.

 

• 편리한 Dual OS 기능(※ 일부 기종 지원)

 

스위치의 일부 기종은 두 가지의 NOS 이미지를 저장할 수 있습니다. NOS 이미지를 두 가지를 저장하여 사용하기 때문에 하나의 NOS 이미지만 저장하여 사용할 때보다 사용자 환경에 따라 알맞은 이미지 파일을 재빠르게 대응할 수 있습니다.

 

• 802.1x 기반 사용자 인증

 

스위치는 IEEE 802.1x를 기반으로 한 사용자 인증 정책을 포트별로 설정할 수 있습니다. 802.1x를 설정한 사용자 인증 포트는 RADIUS 서버를 통해 접속 권한 여부를 판단, 권한을 가지고 있는 접속자만 사용이 가능하기 때문에 네트워크 관리의 보안과 이동성을 높일 수 있습니다.

 

• RADIUS 및 TACACS+

 

스위치는 사용자 인증 프로토콜로 RADIUS(Remote Authentication Dial-In User Service)와 Tacacs+(Terminal Access Controller Access Control System+)를 지원합니다. 스위치에 등록되어 있는 사용자 ID와 Password 이외에도 RADIUS 서버와 TACACS+ 서버를 통하여 인증을 받아야 하기 때문에 시스템 관리 및 네트워크 관리의 보안성을 높였습니다.

 

• SSH 서버

 

스위치는 SSH(Secure Shell) 서버를 활성화 함으로써 telnet, ftp 서비스에 보안성을 높일 수 있습니다.

 

• 브로드캐스트 Storm Control

 

브로드캐스트 Storm이란, 다량의 브로드캐스트 패킷이 네트워크상에 전송되면서 전송 용량의 대부분을 점유함에 따라 네트워크 타임 아웃이 발생하는 현상을 말합니다. 스위치는 사용자가 설정한 시간동안 한계 값을 넘는 브로드캐스트 패킷, 멀티캐스트 패킷, 그리고 DLF 패킷을 폐기하는 브로드캐스트 Storm Control을 지원합니다

'Network' 카테고리의 다른 글

네트워크 프로토콜 종류  (0) 2020.07.01
텔넷 테스트 방법 문의, 포트 오픈 테스트  (1) 2017.10.28
TX RX란?  (0) 2014.03.11
traceroute CISCO 중단 명령어  (0) 2014.03.10
TCP 3way handshaking  (0) 2014.03.09

지역

postal code

강남구

ASI|KR|KS013|Gangnam-Ku

강릉시

ASI|KR|KS007|KANGNUNG

거제시

ASI|KR|KS011|GEOJE

경산시

ASI|KR|KS011|GYEONGSAN

경주시

ASI|KR|KSxx010|KYONG JU

고성시

ASI|KR|KS011|GOSEONG

고양시

ASI|KR|KS009|GOYANG

고흥시

ASI|KR|KS005|GOHEUNG

공주시

ASI|KR|KS002|GONGJU

과천시

ASI|KR|KS009|GWACHEON

광명시

ASI|KR|KS009|GWANGMYEONG

광양시

ASI|KR|KS005|GWANGYANG

광주광역시

ASI|KR|KS008|KWANGCHU

괴산시

ASI|KR|KS001|GOESAN

구리시

ASI|KR|KS013|GURI

구미시

ASI|KR|KS010|GUMI

군산시

ASI|KR|KS004|KUNSAN

군포시

ASI|KR|KS013|GUNPO

김제시

ASI|KR|KS004|GIMJE

김포시

ASI|KR|KS009|GIMPO

김해시

ASI|KR|KS011|KIMHAE

나주시

ASI|KR|KS005|NAJU

남양주시

ASI|KR|KS009|NAMYANGJU

남원시

ASI|KR|KS004|NAMWON

남해시

ASI|KR|KS011|NAMHAE

논산시

ASI|KR|KS002|NONSAN

대구광역시

ASI|KR|KS002|TAEGU

대전광역시

ASI|KR|KS015|DAEJEON

동두천시

ASI|KR|KS009|DONGDUCHEON

동해시

ASI|KR|KS007|DONGHAE

마산시

ASI|KR|KS011|MASAN

목포시

ASI|KR|KS005|MOKPO

무안시

ASI|KR|KS005|MUAN

무주시

ASI|KR|KS004|MUJU

문경시

ASI|KR|KS010|MUNGYEONG

밀양시

ASI|KR|KS011|MIRYANG

보령시

ASI|KR|KS002|BORYEONG

보성시

ASI|KR|KS005|BOSEONG

부산광역시

ASI|KR|KS012|PUSAN

부천시

ASI|KR|KS009|BUCHEON

사천시

ASI|KR|KS011|SACHEON

삼척시

ASI|KR|KS007|SAMCHEOK

상주시

ASI|KR|KS010|SANGJU

서귀포시

ASI|KR|KS003|SEOGWI

서산시

ASI|KR|KS002|SEOSAN

서울특별시

ASI|KR|KS013|SEOUL

서초구

ASI|KR|KS013|Seocho

성남시

ASI|KR|KS009|SEONGNAM

속초시

ASI|KR|KS007|SOKCHO

송도신도시

ASI|KR|KS000|SONGDO

수원시

ASI|KR|KS002|SUWON

순천시

ASI|KR|KS005|SUNCHEON

시흥시

ASI|KR|KS010|SIHEUNG

신안시

ASI|KR|KS005|SINAN

아산시

ASI|KR|KS002|ASAN

안동시

ASI|KR|KS010|ANDONG

안산시

ASI|KR|KS009|ANSAN

안성시

ASI|KR|KS009|ANSEONG

안양시

ASI|KR|KS009|ANYANG

양산시

ASI|KR|KS011|YANGSAN

양수리

ASI|KR|KS009|YANGSU-RI

양양시

ASI|KR|KS007|YANGYANG

양주시

ASI|KR|KS001|YANGJU

여수시

ASI|KR|KS005|YOSU

영양시

ASI|KR|KS010|YEONGYANG

영주시

ASI|KR|KS010|YEONGJU

영천시

ASI|KR|KS010|YEONGCHEON

예산시

ASI|KR|KS002|YESAN

예천시

ASI|KR|KS010|YECHEON

오산시

ASI|KR|KS009|OSAN

온양시

ASI|KR|KS002|ONYANG

완도시

ASI|KR|KS005|WANDO

용인시

ASI|KR|KS009|YONGIN

울산광역시

ASI|KR|KS016|ULSAN

울진시

ASI|KR|KS010|ULJIN

원주시

ASI|KR|KS007|WONJU

의왕시

ASI|KR|KS010|UIWANG

의정부시

ASI|KR|KS009|UIJEONGBU

익산시

ASI|KR|KS004|IKSAN

인제시

ASI|KR|KS007|INJE

인천광역시

ASI|KR|KS006|INCHEON

일산시

ASI|KR|KS007|ILSAN

임실시

ASI|KR|KS004|IMSIL

전주시

ASI|KR|KS004|JEONJU

정읍시

ASI|KR|KS004|JEONGEUP

제주시

ASI|KR|KS003|CHEJU

제천시

ASI|KR|KS001|JECHEON

진도시

ASI|KR|KS010|JINDO

진주시

ASI|KR|KS011|JINJU

진해시

ASI|KR|KS011|JINHAE

천안시

ASI|KR|KS002|CHEONAN

청주시

ASI|KR|KS001|CHEONGJU

춘천시

ASI|KR|KS001|CHUNCHEON

충주시

ASI|KR|KS001|CHUNGJU

태백시

ASI|KR|KS007|TAEBAEK

태안시

ASI|KR|KS016|TAEAN

통영시

ASI|KR|KS005|TONGYEONG

파주시

ASI|KR|KS009|PAJU

평창시

ASI|KR|KS007|PYEONGCHANG

평택시

ASI|KR|KS009|PYEONGTAEK

포천시

ASI|KR|KS009|POCHEON

포항시

ASI|KR|KS010|POHANG

하남시

ASI|KR|KS009|HANAM

하동시

ASI|KR|KS011|HADONG

함안시

ASI|KR|KS011|HAMAN

함양시

ASI|KR|KS011|HAMYANG

합천시

ASI|KR|KS011|HABCHEON

해남시

ASI|KR|KS005|HAENAM

화성시

ASI|KR|KS009|HWASEONG

화순시

ASI|KR|KS005|HWASUN

화천군

ASI|KR|KS007|HWACHEON

 

'상식' 카테고리의 다른 글

신한은행 쏠퀴즈  (0) 2022.04.23
개조식 이란  (0) 2020.04.28
실연에 대처하는 뇌의 자세  (0) 2017.01.09
노력에 대한 명언  (0) 2015.10.16
POP 우리V 체크카드  (0) 2015.10.02

아래 그림처럼 config.php 파일 상단에 나와있습니다.

There is above in config.php file like this below capture

 

'Programing' 카테고리의 다른 글

구글 캘린더 API 자바 연동  (14) 2019.04.12
php 프로그래밍 게시판 사진 보이게 하는 소스코드  (0) 2016.03.27
알고리즘의 종류  (0) 2015.10.18
br 태그  (0) 2015.10.18
배치파일 명령어  (0) 2014.03.13

http://naver.me/xnEFaMGO

This is Korea traditional garden.

 

 

 

 

24시간 무료 상담 080-841-2000

24시간 가입을 원하신다면?

다이렉트 실비보험 가입하러가기

무배당프로미라이프실손의료비보험1904

준법감시인확인필_제2019-1242(2019.01.29)

DB손해보험 실손의료비보험

국민건강보험 비급여 항목보장으로 병원비 부담 덜어드립니다. (일부 자기부담금 공제)

  • 질병/상해 입원일당 및
    통원 외래비용 보장(특약)
  • 비급여 도수치료, 체외충격파
    증식치료의료비 보장(특약)
  • 비급여 주사의료비 보장
    (특약)
  • 비급여 자기공명영상진단
    (MRI/MRA)보장

 실비보험료 간편 조회

남여

 

 

개인정보 수집 이용 동의자세히보기 >

개인정보 수집 위탁 동의자세히보기 >

보험료 계산하기

홍길동 님의
예상 실비보험료는
 00,000원 입니다

무료 상담신청

정상 상담 완료시, 신세계 상품권 5,000원 증정 (제공주체:DB손해보험)

 

DB손해보험 대표이사 사장|김정남 사업자등록번호|201-81-45593(주)DB손해보험 서울시 강남구 테헤란로 432 (대치동DB금융센터) (우)06194

COPYRIGHT 2018 DB INSURANCE.CO.LTD.ALL RIGHTS RESERVED

 

출처

https://www.idirectdb.com/accident/?JEHUSA_CD=C5047&utm_source=tm_google&utm_medium=cpc&utm_campaign=tm_pc&utm_content=accident&utm_term=%EC%8B%A4%EB%B9%84%EB%B3%B4%ED%97%98&gclid=Cj0KCQjwhuvlBRCeARIsAM720HpUzea0ohPXt38pqLBTuY_BNakrJiXfw0D80R-UtFTVd349lFOhIqsaAu9eEALw_wcB

'보험' 카테고리의 다른 글

삼성생명 건강보험  (0) 2015.10.17
eYou 다이렉트 자동차보험  (0) 2015.10.17
현대해상 hicar 운전자보험  (0) 2015.10.17
자동차 보험  (0) 2015.10.17
ace 보험  (0) 2015.10.17

구글 캘린더 API를 자바 기준으로 작성해보겠습니다.

 

목차

1. 구글캘린더 API 연동

2. Controller구현

3. 화면구현

4. 실행화면

 

 

1. 구글캘린더 API 연동

밑에 google API 콘솔로 들어갑니다

https://console.developers.google.com/?hl=ko

 

 

프로젝트 만들기 클릭

 

 

프로젝트를 만들어줍니다.

 

 

라이브러리 -> Calendar API 클릭

 

 

사용 설정 클릭

 

 

사용자 인증 정보 만들기 클릭

 

 

java로 구현하기때문에 웹서버 -> 애플리케이션 데이터 -> 아니요 -> 사용자 인정 정보 클릭

 

 

서비스 계정 이름은 아무거나 적고 -> 역할도 적당히 설정 -> 키 유형 JSON 체크 -> 계속

 

 

사용자 동의화면을 정해줍니다. -> 저장클릭

 

 

사용자 인증 정보 만들기 -> OAuth 클라이언트 ID 클릭

 

 

기타유형 -> 생성

 

 

접속정보 JSON을 다운 받습니다.

 

 

파일명을 변경

 

 

client_secret.json

 

 

src/main/resources 경로에 넣어줍니다.

 

 

dependency

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

        <!-- google api -->

        <dependency>

            <groupId>com.google.api-client</groupId>

            <artifactId>google-api-client</artifactId>

            <version>1.22.0</version>

        </dependency>

 

        <dependency>

            <groupId>com.google.oauth-client</groupId>

            <artifactId>google-oauth-client-jetty</artifactId>

            <version>1.22.0</version>

        </dependency>

 

        <dependency>

            <groupId>com.google.apis</groupId>

            <artifactId>google-api-services-calendar</artifactId>

            <version>v3-rev235-1.22.0</version>

        </dependency>

Colored by Color Scripter

cs

 

 

 

 

 

java로 접속할 객체를 만들어줍니다.

 

 

GoogleCalendarService.java

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

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

package com.t.hc.beans;

 

import java.io.IOException;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.util.Arrays;

import java.util.List;

 

import com.google.api.client.auth.oauth2.Credential;

import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;

import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;

import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;

import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;

import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;

import com.google.api.client.http.HttpTransport;

import com.google.api.client.json.JsonFactory;

import com.google.api.client.json.jackson2.JacksonFactory;

import com.google.api.client.util.store.FileDataStoreFactory;

import com.google.api.services.calendar.Calendar;

import com.google.api.services.calendar.CalendarScopes;

//import com.google.api.services.calendar.model.CalendarList;

//import com.google.api.services.calendar.model.CalendarListEntry;

 

public class GoogleCalendarService {

 

    private static final String APPLICATION_NAME = "Google Calendar API Java Quickstart";

 

    private static final java.io.File DATA_STORE_DIR = new java.io.File(

            System.getProperty("user.home"),

            ".credentials/calendar-java-quickstart");

 

    private static FileDataStoreFactory DATA_STORE_FACTORY;

 

    private static final JsonFactory JSON_FACTORY = JacksonFactory

            .getDefaultInstance();

 

    private static HttpTransport HTTP_TRANSPORT;

 

    private static final List<String> SCOPES = Arrays

            .asList(CalendarScopes.CALENDAR);

 

    static {

        try {

            HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();

            DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);

        } catch (Throwable t) {

            t.printStackTrace();

            System.exit(1);

        }

    }

 

    public static Credential authorize() throws IOException {

        InputStream in = GoogleCalendarService.class

                .getResourceAsStream("/client_secret.json");

        GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(

                JSON_FACTORY, new InputStreamReader(in));

 

        GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(

                HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)

                .setDataStoreFactory(DATA_STORE_FACTORY)

                .setAccessType("offline").build();

        Credential credential = new AuthorizationCodeInstalledApp(flow,

                new LocalServerReceiver()).authorize("user");

        System.out.println("Credentials saved to "

                + DATA_STORE_DIR.getAbsolutePath());

        return credential;

    }

 

    public static Calendar getCalendarService() throws IOException {

        Credential credential = authorize();

        return new Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)

                .setApplicationName(APPLICATION_NAME).build();

    }

 

//    public static void main(String[] args) throws IOException {

//        com.google.api.services.calendar.Calendar service = getCalendarService();

//        // 캘린더 조회

//        String pageToken = null;

//        do {

//          CalendarList calendarList = service.calendarList().list().setPageToken(pageToken).execute();

//          List<CalendarListEntry> items1 = calendarList.getItems();

//

//          for (CalendarListEntry calendarListEntry : items1) {

//            System.out.println(calendarListEntry.getSummary());

//            System.out.println(calendarListEntry.getId());

//          }

//          pageToken = calendarList.getNextPageToken();

//        } while (pageToken != null);

//    }

}

Colored by Color Scripter

cs

 

 

 

 

 

접속 테스트를위해 main메소드를 돌려봅니다.

 

 

성공적으로 수행되면 최초호출시 권한요청 브라우저 창이 뜸니다.

 

 

성공적으로 calendar 목록데이터를 가져온걸 볼수있습니다.

 

 

CalendarDto.java 작성

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

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

package com.t.hc.dto;

 

import java.text.ParseException;

import java.text.SimpleDateFormat;

import java.util.Date;

 

public class CalendarDto {

    

    private String summary;

    private String startDate;

    private String startTime;

    private String endDate;

    private String endTime;

    private String description;

    private String eventId;

    private String calendarId;

    

    {

        description = "";

    }

    

    public CalendarDto() {}

    

    public Date getStartDateTime() throws ParseException {

        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-ddHH:mm");

        return format.parse(startDate+startTime);

    }

    public Date getEndDateTime() throws ParseException {

        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-ddHH:mm");

        return format.parse(endDate+endTime);

    }

    

    public String getSummary() {

        return summary;

    }

    public void setSummary(String summary) {

        this.summary = summary;

    }

    public String getStartDate() {

        return startDate;

    }

    public void setStartDate(String startDate) {

        this.startDate = startDate;

    }

    public String getStartTime() {

        return startTime;

    }

    public void setStartTime(String startTime) {

        this.startTime = startTime;

    }

    public String getEndDate() {

        return endDate;

    }

    public void setEndDate(String endDate) {

        this.endDate = endDate;

    }

    public String getEndTime() {

        return endTime;

    }

    public void setEndTime(String endTime) {

        this.endTime = endTime;

    }

    public String getDescription() {

        return description;

    }

    public void setDescription(String description) {

        this.description = description;

    }

    public String getEventId() {

        return eventId;

    }

    public void setEventId(String eventId) {

        this.eventId = eventId;

    }

    public String getCalendarId() {

        return calendarId;

    }

    public void setCalendarId(String calendarId) {

        this.calendarId = calendarId;

    }

 

    @Override

    public String toString() {

        return "GoogleCalendarDto [summary=" + summary + ", startDate=" + startDate + ", startTime=" + startTime

                + ", endDate=" + endDate + ", endTime=" + endTime + ", description=" + description + ", eventId="

                + eventId + ", calendarId=" + calendarId + "]";

    }

}

Colored by Color Scripter

cs

 

 

 

2. Controller구현

캘린더 리스트 Controller 작성

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

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

package com.t.hc;

 

import java.io.IOException;

import java.util.List;

 

import javax.servlet.http.HttpServletRequest;

 

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

 

import com.google.api.services.calendar.Calendar;

import com.google.api.services.calendar.model.CalendarList;

import com.google.api.services.calendar.model.CalendarListEntry;

import com.t.hc.beans.GoogleCalendarService;

import com.t.hc.dto.CalendarDto;

 

@Controller

public class HandcodingController {

    

    private Logger logger = LoggerFactory.getLogger(HandcodingController.class);

    

    // 캘린더리스트

    @RequestMapping(value="/coding.do", method=RequestMethod.GET)

    public String coding(Model model) {

        logger.info("calendarList");

        try {

            Calendar service = GoogleCalendarService.getCalendarService();

            CalendarList calendarList = service.calendarList().list().setPageToken(null).execute();

            List<CalendarListEntry> items = calendarList.getItems();

            model.addAttribute("items", items);

        } catch (IOException e) {

            e.printStackTrace();

        }

        return "coding";

    }

    

    // 캘린더 생성 처리

    @RequestMapping(value="/calendarAdd.do", method=RequestMethod.POST)

    public String calendarAdd(CalendarDto calDto) {

        logger.info("calendarAdd "+calDto.toString());

        

        try {

            Calendar service = GoogleCalendarService.getCalendarService();

            com.google.api.services.calendar.model.Calendar calendar = new com.google.api.services.calendar.model.Calendar();

            calendar.setSummary(calDto.getSummary());

            calendar.setTimeZone("America/Los_Angeles");

            service.calendars().insert(calendar).execute();

        } catch (IOException e) {

            e.printStackTrace();

        }

        return "redirect:/coding.do";

    }

    

    // 캘린더 삭제 처리

    @RequestMapping(value="/calendarRemove.do", method=RequestMethod.POST)

    public String calendarRemove(HttpServletRequest req) {

        logger.info("calendarRemove");

        

        String[] chkVal = req.getParameterValues("chkVal");

        try {

            Calendar service = GoogleCalendarService.getCalendarService();

            for (String calendarId : chkVal) {

                service.calendars().delete(calendarId).execute();

            }

        } catch (IOException e) {

            e.printStackTrace();

        }

        return "redirect:/coding.do";

    }    

    

    // 캘린더 수정 처리

    @RequestMapping(value="/calendarModify.do", method=RequestMethod.POST)

    public String calendarModify(CalendarDto calDto) {

        logger.info("calendarModify "+calDto.toString());

        

        try {

            Calendar service = GoogleCalendarService.getCalendarService();

            com.google.api.services.calendar.model.Calendar calendar = service.calendars().get(calDto.getCalendarId()).execute();

            calendar.setSummary(calDto.getSummary());

            service.calendars().update(calendar.getId(), calendar).execute();

        } catch (IOException e) {

            e.printStackTrace();

        }

        return "redirect:/coding.do";

    }    

    

    // 캘린더 이동처리

    @RequestMapping(value="/schdule.do", method=RequestMethod.GET)

    public String schdule(Model model, String calendarId, String title) {

        logger.info("schdule");

        model.addAttribute("calendarId", calendarId);

        model.addAttribute("title", title);

        return "schdule";

    }    

}

 

Colored by Color Scripter

cs

 

 

 

 

일정이벤트 핸들링할 ajax컨트롤러 작성

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

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

package com.t.hc;

 

import java.io.IOException;

import java.text.ParseException;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

 

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RestController;

 

import com.google.api.client.util.DateTime;

import com.google.api.services.calendar.Calendar;

import com.google.api.services.calendar.model.Event;

import com.google.api.services.calendar.model.EventDateTime;

import com.google.api.services.calendar.model.Events;

import com.t.hc.beans.GoogleCalendarService;

import com.t.hc.dto.CalendarDto;

 

@RestController

public class CalendarAjaxController {

    

    private Logger logger = LoggerFactory.getLogger(CalendarAjaxController.class);

    

    // 일정 데이터 처리

    @RequestMapping(value="/calendarEventList.do", method=RequestMethod.POST)

    public List<Event> calendarEventList(CalendarDto calDto) {

        logger.info("calendarEventList "+calDto.toString());

        

        List<Event> items = new ArrayList<Event>();

        try {

            com.google.api.services.calendar.Calendar service = GoogleCalendarService.getCalendarService();

            Events events = service.events().list(calDto.getCalendarId()).setOrderBy("startTime").setSingleEvents(true).execute();

            items = events.getItems();

        } catch (IOException e) {

            e.printStackTrace();

        }

        return items;

    }

    

    // 일정 저장 처리

    @RequestMapping(value="/calendarEventAdd.do", method=RequestMethod.POST)

    public Map<String, Boolean> calendarEventAdd(CalendarDto calDto) {

        logger.info("calendarEventAdd "+calDto.toString());

        

        boolean isc = false;

        try {

            Calendar service = GoogleCalendarService.getCalendarService();

            Event event = new Event().setSummary(calDto.getSummary()).setDescription(calDto.getDescription());

            //시작일

            DateTime startDateTime = new DateTime(calDto.getStartDateTime());

            EventDateTime start = new EventDateTime().setDateTime(startDateTime).setTimeZone("America/Los_Angeles");

            event.setStart(start);

            //종료일

            DateTime endDateTime = new DateTime(calDto.getEndDateTime());

            EventDateTime end = new EventDateTime().setDateTime(endDateTime).setTimeZone("America/Los_Angeles");

            event.setEnd(end);

            event = service.events().insert(calDto.getCalendarId(), event).execute();

            isc = true;

        } catch (IOException | ParseException e) {

            e.printStackTrace();

        }

        Map<String, Boolean> map = new HashMap<String, Boolean>();

        map.put("isc", isc);

        return map;

    }    

    

    // 일정 삭제

    @RequestMapping(value="/calendarEventRemoveOne.do", method=RequestMethod.POST)

    public Map<String, Boolean> calendarEventRemoveOne(CalendarDto calDto) {

        logger.info("calendarEventRemoveOne "+calDto.toString());

        

        boolean isc = false;

        try {

            Calendar service = GoogleCalendarService.getCalendarService();

            service.events().delete(calDto.getCalendarId(), calDto.getEventId()).execute();

            isc = true;

        } catch (IOException e) {

            e.printStackTrace();

        }

        Map<String, Boolean> map = new HashMap<String, Boolean>();

        map.put("isc", isc);

        return map;

    }

    

    // 일정 수정

    @RequestMapping(value="/calendarEventModify.do", method=RequestMethod.POST)

    public Map<String, Boolean> calendarEventModify(CalendarDto calDto) {

        logger.info("calendarEventModify "+calDto.toString());

        

        boolean isc = false;

        try {

            Calendar service = GoogleCalendarService.getCalendarService();

            Event event = service.events().get(calDto.getCalendarId(), calDto.getEventId()).execute();

            event.setSummary(calDto.getSummary()).setDescription(calDto.getDescription());

            service.events().update(calDto.getCalendarId(), event.getId(), event).execute();

            isc = true;

        } catch (IOException e) {

            e.printStackTrace();

        }

        Map<String, Boolean> map = new HashMap<String, Boolean>();

        map.put("isc", isc);

        return map;

    }

}

Colored by Color Scripter

cs

 

 

 

 

3. 화면구현

캘린더 리스트화면

coding.jsp

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

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<!DOCTYPE html>

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>캘린더 관리</title>

<script src="./js/jquery-3.1.1.min.js"></script>

<script src="./js/bootstrap.min.js"></script>

<link rel="stylesheet" href="./css/bootstrap.min.css">

<link rel="stylesheet" href="./css/bootstrap-theme.min.css">

<script type="text/javascript" src='./js/sweetalert.min.js?ver=1'></script>

<link rel="stylesheet" type="text/css" href='./css/sweetalert.css?ver=1.2'>

<script type="text/javascript" src="./js/calendarList.js"></script>

</head>

<body>

    <form action="./calendarRemove.do" method="post" id="frmCalendarRemove">

        <table class="table table-bordered">

            <tr>

                <th><input type='checkbox' onclick='checkAllDel(this.checked)' />전체</th>

                <th>캘린더이름</th>

                <th>캘린더코드</th>

            </tr>

            <c:forEach items="${items}" var="item">

                <tr>

                    <td><input type='checkbox' name='chkVal' value="${item.id}" /></td>

                    <td><input type="hidden" name='summarys' value="${item.summary}" />

                        <a href="./schdule.do?calendarId=${item.id}&title=${item.summary}">${item.summary}</a>

                    </td>

                    <td>${item.id}</td>

                </tr>

            </c:forEach>

        </table>

    </form>

    <input type="button" class='btn btn-sm btn-warning' value="캘린더 생성"

        onclick="calendarAddForm()" />

    <input type="button" class='btn btn-sm btn-warning' value="캘린더 수정"

        onclick="calendarModifyForm()" />

    <input type="button" class='btn btn-sm btn-warning' value="캘린더 삭제"

        onclick="calendarRemove()" />

    <!-- 캘린더 생성 modal -->

    <div class="modal fade" id="calendarAddForm" role="dialog">

        <div class="modal-dialog">

            <div class="modal-content">

                <div class="modal-header">

                    <button type="button" class="close" data-dismiss="modal">×</button>

                    <h4 class="modal-title">캘린더 생성</h4>

                </div>

                <div class="modal-body">

                    <!-- 캘린더 생성처리 form -->

                    <form action="./calendarAdd.do" method='post' id='frmCalendarAdd'>

                        <div class='form-group'>

                            <label>캘린더이름</label><input class='form-control' type="text"

                                name='summary' id='summary' />

                        </div>

                        <div class='modal-footer'>

                            <input type="button" class='btn btn-sm btn-warning' value="확인"

                                onclick="calendarAdd()" /> <input type="reset"

                                class='btn btn-sm btn-warning' value="초기화" /> <input

                                type='button' class='btn btn-sm btn-warning'

                                data-dismiss='modal' value="취소" />

                        </div>

                    </form>

                </div>

            </div>

        </div>

    </div>

    <!-- 캘린더 수정 modal -->

    <div class="modal fade" id="calendarModifyForm" role="dialog">

        <div class="modal-dialog">

            <div class="modal-content">

                <div class="modal-header">

                    <button type="button" class="close" data-dismiss="modal">×</button>

                    <h4 class="modal-title">캘린더 수정</h4>

                </div>

                <div class="modal-body">

                    <!-- 캘린더 생성처리 form -->

                    <form action="./calendarModify.do" method='post'

                        id='frmCalendarModify'>

                        <div class='form-group'>

                            <label>캘린더이름</label><input class='form-control' type="text"

                                name='summary' id='summaryModify' />

                        </div>

                        <input type="hidden" name="calendarId" id='calendarIdModify' />

                        <div class='modal-footer'>

                            <input type="button" class='btn btn-sm btn-warning' value="확인"

                                onclick="calendarModify()" /> <input type="reset"

                                class='btn btn-sm btn-warning' value="초기화" /> <input

                                type='button' class='btn btn-sm btn-warning'

                                data-dismiss='modal' value="취소" />

                        </div>

                    </form>

                </div>

            </div>

        </div>

    </div>

</body>

</html>

Colored by Color Scripter

cs

 

 

 

 

캘린더 일정화면

schdule.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html>

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>일정관리</title>

<script src="./js/jquery-3.1.1.min.js"></script>

<script src="./js/bootstrap.min.js"></script>

<link rel="stylesheet" href="./css/bootstrap.min.css">

<link rel="stylesheet" href="./css/bootstrap-theme.min.css">

<script type="text/javascript" src='./js/sweetalert.min.js?ver=1'></script>

<link rel="stylesheet" type="text/css"

    href='./css/sweetalert.css?ver=1.2'>

<script type="text/javascript" src="./js/stringBuffer.js"></script>

<script type="text/javascript" src="./js/calendar.js"></script>

<script type="text/javascript" src="./js/calendarSchdule.js"></script>

<style type="text/css">

thead {

    text-align: center;

}

thead td {

    width: 100px;

}

#tbody td {

    height: 150px;

}

#yearMonth {

    font: bold;

    font-size: 18px;

}

</style>

</head>

<body>

    <input type="hidden" id="chk" value="0" />

    <input type="hidden" id="calendarId" value="${calendarId}" />

    <table class="table table-bordered">

        <thead id='thead'>

            <tr>

                <td colspan="7">

                    <button type='button' class='btn btn-sm btn-warning'

                        id='moveFastPre' onclick="moveFastMonthPre()">«</button>

                     

                    <button type='button' class='btn btn-sm btn-warning' id='movePre'

                        onclick="moveMonthPre()"></button>    <span

                    id='yearMonth'></span>   

                    <button type='button' class='btn btn-sm btn-warning' id='moveNext'

                        onclick="moveMonthNext()"></button>  

                    <button type='button' class='btn btn-sm btn-warning'

                        id='moveFastNext' onclick="moveFastMonthNext()">»</button>

                    <div style="text-align: right;">

                        <span>${title}</span> <input class='btn btn-sm btn-info'

                            type="button" value="주" onclick='tabWeek()' /> <input

                            class='btn btn-sm btn-info' type="button" value="월"

                            onclick='tabMonth()' /> <input class='btn btn-sm btn-info'

                            type="button" value="목록" onclick='location.href="./coding.do"' />

                    </div>

                </td>

            </tr>

            <tr>

                <td><span class='week'></span></td>

                <td><span class='week'></span></td>

                <td><span class='week'></span></td>

                <td><span class='week'></span></td>

                <td><span class='week'></span></td>

                <td><span class='week'></span></td>

                <td><span class='week'></span></td>

            </tr>

        </thead>

        <tbody id='tbody'></tbody>

    </table>

    <!-- 일정 생성 modal -->

    <div class="modal fade" id="schduleForm" role="dialog">

        <div class="modal-dialog">

            <div class="modal-content">

                <div class="modal-header">

                    <button type="button" class="close" data-dismiss="modal">×</button>

                    <h4 class="modal-title">일정등록</h4>

                </div>

                <div class="modal-body">

                    <form class='form-margin40' role='form' action="#" method='post'

                        id='frmSchdule'>

                        <div class='form-group'>

                            <label>제목</label> <input type='text' class='form-control'

                                id='summary' name='summary'

                                placeholder="예: 오후 7시에 멕시코 음식점에서 저녁식사">

                        </div>

                        <div class='form-group'>

                            <label>시작시간</label> <input class='form-control' type="time"

                                id='startTime' name='startTime'>

                        </div>

                        <div class='form-group'>

                            <label>시작날짜</label> <input class='form-control startDate'

                                type="date" id='startDate' name='startDate' readonly="readonly">

                        </div>

                        <div class='form-group'>

                            <label>종료시간</label> <input class='form-control' type="time"

                                id='endTime' name='endTime'>

                        </div>

                        <div class='form-group'>

                            <label>종료날짜</label> <input class='form-control startDate'

                                type="date" id='endDate' name='endDate'>

                        </div>

                        <div class='form-group'>

                            <label>내용</label>

                            <textarea rows="7" class='form-control' id="description"

                                name='description'></textarea>

                        </div>

                        <div class='modal-footer'>

                            <input type="button" class='btn btn-sm btn-warning' value="확인"

                                onclick="calendarSchduleAdd()" /> <input type="reset"

                                class='btn btn-sm btn-warning' value="초기화" /> <input

                                type='button' class='btn btn-sm btn-warning'

                                data-dismiss='modal' value="취소" />

                        </div>

                    </form>

                </div>

            </div>

        </div>

    </div>

    <!-- 일정 수정 modal -->

    <div class="modal fade" id="schduleFormModify" role="dialog">

        <div class="modal-dialog">

            <div class="modal-content">

                <div class="modal-header">

                    <button type="button" class="close" data-dismiss="modal">×</button>

                    <h4 class="modal-title">일정수정</h4>

                </div>

                <div class="modal-body">

                    <form class='form-margin40' role='form' action="#" method='post'

                        id='frmSchduleModify'>

                        <div class='form-group'>

                            <label>제목</label> <input type='text' class='form-control'

                                id='modifySummary' name='summary'>

                        </div>

                        <div class='form-group'>

                            <label>내용</label>

                            <textarea rows="7" class='form-control' id="modifyDescription"

                                name='description'></textarea>

                        </div>

                        <input type="hidden" id="modifyEventId" name="eventId" /> <input

                            type="hidden" name="calendarId" value="${calendarId}" />

                        <div class='modal-footer'>

                            <input type="button" class='btn btn-sm btn-warning' value="확인"

                                onclick="modifyEvent()" /> <input type="reset"

                                class='btn btn-sm btn-warning' value="초기화" /> <input

                                type='button' class='btn btn-sm btn-warning'

                                data-dismiss='modal' value="취소" />

                        </div>

                    </form>

                </div>

            </div>

        </div>

    </div>

</body>

</html>

Colored by Color Scripter

cs

 

 

 

 

화면 라이브러리는 부트스트랩, jQuery, sweetalert을 씁니다.

 

 

기타 util javaScript

stringBuffer.js

1

2

3

4

5

6

7

8

9

10

// StringBuffer

var StringBuffer = function() {

    this.buffer = new Array();

};

StringBuffer.prototype.append = function(str) {

    this.buffer[this.buffer.length= str;

};

StringBuffer.prototype.toString = function() {

    return this.buffer.join("");

};

Colored by Color Scripter

cs

 

 

 

 

달력연산객체를 하나 만들어줍니다.

calendar.js

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

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

// 캘린더 객체

var calendar = {

    LEAF : [ 312931303130313130313031 ], //윤년

    PLAIN : [ 312831303130313130313031 ], //평년

    iscLeafCheck :

    //윤년 판단

    function(year) {

        var isc = false;

        if ((year % 4 == 0 && year % 100 != 0|| year % 400 == 0) { // 윤년이면

            isc = true;

        }

        return isc;

    },

    daysY :

    //년도에 따른 일수 누적

    function(year) {

        var daySum = 0;

        for (var i = 1; i < year; i++) {

            if (this.iscLeafCheck(i)) {

                daySum += 366;

            } else {

                daySum += 365;

            }

        }

        return daySum;

    },

    daysM :

    //년도누적 + 월 일수 누적

    function(year, month) {

        var daySum = this.daysY(year);

        for (var i = 1; i < month; i++) {

            daySum += this.PLAIN[i - 1];

        }

        if (month >= 2 && this.iscLeafCheck(year)) {

            daySum++;

        }

        return daySum;

    },

    daysD :

    //년도누적 + 월 누적 + 일수 누적

    function(year, month, day) {

        return this.daysM(year, month) + day;

    },

    lastDay :

    // 구하고자 하는 년월의 최대 일수

    function(year, month) {

        var last_day = 0;

        if (this.iscLeafCheck(year)) {

            last_day = this.LEAF[month - 1];

        } else {

            last_day = this.PLAIN[month - 1];

        }

        return last_day;

    },

    isBeforeDays :

    // 앞의 달에 년도 분기

    function(year, month) {

        var days = 0;

        if (month == 1) {

            days = this.lastDay(year - 112);

        } else {

            days = this.lastDay(year, month - 1);

        }

        return days;

    },

    make :

    // 해당달력을 배열로 반환

    function(year, month) {

        var dateOfWeek = (this.daysD(year, month, 1)) % 7;

        var beforeLastDay = this.isBeforeDays(year, month);

        var startLastDay = beforeLastDay - dateOfWeek + 1;

        var last_day = this.lastDay(year, month); // 구하고자 하는 년월의 최대 일수

        var lastWeekDays = (7 - (dateOfWeek + last_day) % 7) % 7;

        if (this.iscLeafCheck(year)) {

            startLastDay++;

            lastWeekDays++;

        }

        var dayArray = new Array();

        var cnt = 0;

        for (var i = startLastDay; i <= beforeLastDay; i++, cnt++) {

            dayArray[cnt] = i;

        }

        for (var i = 1; i <= last_day; i++, cnt++) {

            dayArray[cnt] = i;

        }

        for (var i = 1; i <= lastWeekDays; i++, cnt++) {

            dayArray[cnt] = i;

        }

        return dayArray;

    },

    makeOne :

    // 달력 한개만

    function(year, month){

        var last_day = this.lastDay(year, month); // 구하고자 하는 년월의 최대 일수

        var dayArray = new Array();

        var cnt = 0;

        for (var i = 1; i <= last_day; i++, cnt++) {

            dayArray[cnt] = i;

        }

        return dayArray;

    }

}

Colored by Color Scripter

cs

 

 

 

캘린더 리스트 화면을 핸들링할 javaScript

calendarList.js

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

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

// 캘린더 생성폼 호출

function calendarAddForm() {

    $('#calendarAddForm').modal();

}

// 캘린더 생성 처리

function calendarAdd() {

    var summary = $('#summary').val();

    if(summary.trim() == '' || summary.trim().length == 0) {

        swal('이름','입력해주세요');

        return false;

    }

    $('#frmCalendarAdd').submit();

}

//전체체크

function checkAllDel(bool) {

    var chkVal = document.getElementsByName("chkVal");

    for (var i = 0; i < chkVal.length; i++) {

        chkVal[i].checked = bool;

    }

}

//캘린더 삭제

function calendarRemove() {

    var chkVal = document.getElementsByName("chkVal");

    var n = 0;

    for (var i = 0; i < chkVal.length; i++) {

        if(chkVal[i].checked == true){

            n++;

        }

    }

    if(n>0){

        $('#frmCalendarRemove').submit();

    }else {

        swal("캘린더 삭제"'선택해주세요');

    }

}

// 캘린더 수정 호출

function calendarModifyForm() {

    var chkVal = document.getElementsByName("chkVal");

    var summarys = document.getElementsByName("summarys");

    var n = 0;

    var calendarId = '';

    var summary = '';

    for (var i = 0; i < chkVal.length; i++) {

        if(chkVal[i].checked == true){

            n++;

            calendarId = chkVal[i].value;

            summary = summarys[i].value;

        }

    }

    if(n==1) {

        $('#frmCalendarModify').find('#summaryModify').val(summary);

        $('#frmCalendarModify').find('#calendarIdModify').val(calendarId);

    }else if(n>1) {

        swal("캘린더 수정"'1개만 선택해주세요');

        return false;

    }else {

        swal("캘린더 수정"'선택해주세요');

        return false;

    }

    $('#calendarModifyForm').modal();

}

// 캘린더 수정 처리

function calendarModify() {

    $('#frmCalendarModify').submit();

}

 

Colored by Color Scripter

cs

 

 

 

 

 

일정을 핸들링할 js

calendarSchdule.js

var data = {} // List<Event>

var locationMonth = 0// 현재 달력위치

var locationYear = 0// 현재 년도위치

var locationWeek = 0// 현재 주차위치

// 날짜 테그를 만들어준다

function dayTagFormat(year, month, day) {

    var tag = new StringBuffer();

    tag.append("<td id="+year+month+day+">");

    tag.append("<a onclick='schduleAdd("+year+","+month+","+day+")'>"+numFormat(day)+"</a>");

    tag.append("</td>");

    return tag.toString();

}

//숫자 5 -> 05 변경

function numFormat(num) {

    var str = ''+num;

    if(num<10 && str.indexOf('0'== -1 || str.length == 1) {

        str = '0'+num;

    }

    return str;

}

// 시간을 리턴한다

function getTime(item) {

    return numFormat(item.getHours()) + ":" + numFormat(item.getMinutes());

}

// 일정추가 폼

function schduleAdd(year, month, day) {

    $('.startDate').val(year + "-" + numFormat(month) + "-" + numFormat(day));

    $('#summary').val('');

    $('#startTime').val('');

    $('#endTime').val('');

    $('#description').val('');

    $('#schduleForm').modal();

}

// 유효성 검사 일정 저장처리

function calendarSchduleAdd() {

    var summary = $('#summary').val();

    var startTime = $('#startTime').val().split(":");

    var endTime = $('#endTime').val().split(":");

    if(summary.trim() == '' || summary.trim().length == 0) {

        swal('제목','입력해주세요');

        return false;

    }else if($('#startTime').val() == '') {

        swal('시작시간','입력해주세요');

        return false;

    }else if($('#endTime').val() == '') {

        swal('종료시간','입력해주세요');

        return false;        

    }else if(new Date(0,0,0,endTime[0],endTime[1]).getTime() - new Date(0,0,0,startTime[0],startTime[1]).getTime() < 0) {

        swal('시간','종료시간이 시작시간보다 늦습니다');

        return false;

    }else if($('#endDate').val() == '') {

        swal('종료날짜','입력해주세요');

        return false;

    }else if(new Date($('#endDate').val()).getTime() - new Date($('#startDate').val()).getTime() < 0) {

        swal('날짜','종료일이 시작일보다 늦습니다');

        return false;

    }

    $("#schduleForm").modal('hide');

    swal('calendar''google토큰이 필요합니다.');

    $.ajax({

        url: './calendarEventAdd.do',

        type: 'post',

        async: false,

        data : $('#frmSchdule').serialize(),

        success: function(msg) {

            if(msg.isc) {

                swal('저장''성공하였습니다');

            }else {

                swal('저장''실패하였습니다');

            }

        }

    });

    calendarEventList();

    screenWriteMonth();

}

//달력의 해당 날짜의 요일을 구하기위해 현재위치 반환

function monthDayIndex(month, day) {

    for(var i=0; i<month.length; i++) {

        if(month[i]==day) {

            return i;

        }

    }

}

// 달력 이전으로 이동

function moveMonthPre() {

    locationMonth--;

    screenWriteMonth();

}

// 달력 다음으로 이동

function moveMonthNext() {

    locationMonth++;

    screenWriteMonth();

}

// 달력 이전 년도로 이동

function moveFastMonthPre() {

    locationYear--;

    screenWriteMonth();    

}

// 달력 다음 년도로 이동

function moveFastMonthNext() {

    locationYear++;

    screenWriteMonth();        

}

// 화면에 달력과 이벤트를 그려준다

function screenWriteMonth() {

    var date = new Date();

    var month = date.getMonth()+1+locationMonth;

    if(month == 0) {

        locationYear--;

        locationMonth = 12 - Math.abs(locationMonth);

        month = date.getMonth()+1+locationMonth;

    }else if(month == 13) {

        locationYear++;

        locationMonth = locationMonth - 12;

        month = date.getMonth()+1+locationMonth;

    }

    var months = [month-1, month, month+1];

    if(month == 1) {

        months = [12, month, month+1];

    }else if(month == 12) {

        months = [month-1, month, 1];

    }

    var year = date.getFullYear()+locationYear;

    var monthDay = calendar.make(year, months[1]);

    var tag = new StringBuffer();

    var startIndex = monthDayIndex(monthDay, 1);

    var lastIndex = monthDayIndex(calendar.makeOne(year, months[1]), calendar.lastDay(year, months[1])) + startIndex;

    for(var i=0; i<monthDay.length; i++) {

        if(i%7 == 0) {

            tag.append('<tr>');

        }

        if(i<startIndex) {

            if(months[0]==12) {

                tag.append(dayTagFormat(year-1, months[0], monthDay[i]));

            }else {

                tag.append(dayTagFormat(year, months[0], monthDay[i]));

            }

        }else if(i <= lastIndex) {

            tag.append(dayTagFormat(year, months[1], monthDay[i]));

        }else {

            if(months[2]==1) {

                tag.append(dayTagFormat(year+1, months[2], monthDay[i]));

            }else {

                tag.append(dayTagFormat(year, months[2], monthDay[i]));

            }

        }

        if(i%7 == 6) {

            tag.append('</tr>');

        }

    }

    $('#tbody').html(tag.toString());

    $('#yearMonth').text(year + "년 " + numFormat(months[1]) + "월");

    if(data.chk) {

        for(var i=0; i<data.cnt; i++) {

            var itemMonth = data.start[i].getMonth()+1;

            var itemYear = data.start[i].getFullYear();

            if((itemMonth == months[1|| itemMonth == months[0|| itemMonth == months[2])

                    && (itemYear == year || itemYear == year-1 || itemYear == year+1)) {

                $('#'+itemYear+itemMonth+data.start[i].getDate()).append(eventTagFormat(getTime(data.start[i]), data.title[i], data.eventId[i], data.description[i]));

            }

        }

    }

}

// 일정 태그를 만들어 준다

function eventTagFormat(time, title, eventId, description) {

    var tag = new StringBuffer();

    tag.append("<p>");

    tag.append('<a data-toggle="collapse" data-target="#collapseExample'+eventId+'" aria-expanded="false" aria-controls="collapseExample" onclick="collapse(\''+eventId+'\')">');

    tag.append(time+"  "+title);

    tag.append('</a>');

    tag.append('<div class="collapse" id="collapseExample'+eventId+'">');

    if(description == null) {

        tag.append('<div class="well">내용이 없습니다</div>');

    }else {

        tag.append('<div class="well">'+description+'</div>');

    }

    tag.append('<div style="text-align: right;"><input type="button" class="btn btn-sm btn-warning" value="수정" onclick="modifyEventModal(\''+title+'\',\''+eventId+'\',\''+description+'\')"/> ');

    tag.append('<input type="button" class="btn btn-sm btn-warning" value="삭제" onclick="removeEventOne(\''+eventId+'\')"/></div>');

    tag.append('</div>');

    tag.append("</p>");

    return tag.toString();

}

// collapse 처리

function collapse(eventId) {

    $('.collapse').not('#collapseExample'+eventId).each(function(){

        $(this).attr('class''collapse collapse');

    });    

}

// 일정수정 modal

function modifyEventModal(title, eventId, description) {

    $('#modifySummary').val(title);

    if(description != 'undefined') {

        $('#modifyDescription').val(description);

    }else {

        $('#modifyDescription').val('');

    }

    $('#modifyEventId').val(eventId);

    $('#schduleFormModify').modal();

}

// 일정수정 처리

function modifyEvent() {

    var summary = $('#modifySummary').val();

    if(summary.trim() == '' || summary.trim().length == 0) {

        swal('제목','입력해주세요');

        return false;    

    }

    $("#schduleFormModify").modal('hide');

    $.ajax({

        url: './calendarEventModify.do',

        type: 'post',

        async: false,

        data: $('#frmSchduleModify').serialize(),

        success: function(msg) {

            if(msg.isc) {

                swal('수정''성공하였습니다');

            }else {

                swal('수정''실패하였습니다');

            }

        }

    });

    calendarEventList();    

    if($('#chk').val() == '1') {

        screenWriteWeek();

    }else {

        screenWriteMonth();

    }

}

// 일정삭제

function removeEventOne(eventId) {

    $.ajax({

        url: './calendarEventRemoveOne.do',

        type: 'post',

        async: false,

        data : {

            "eventId" : eventId,

            "calendarId" : $('#calendarId').val()

        },

        success: function(msg) {

            if(msg.isc) {

                swal('삭제''성공하였습니다');

            }else {

                swal('삭제''실패하였습니다');

            }

        }

    });

    calendarEventList();

    if($('#chk').val() == '1') {

        screenWriteWeek();

    }else {

        screenWriteMonth();

    }

}

// ajax로 이벤트 데이터를 받는다

function calendarEventList() {

    $.ajax({

        url: './calendarEventList.do',

        type: 'post',

        data: {

            "calendarId" : $('#calendarId').val()

        },

        async: false,

        success: function(lists) {

            if(lists.length != 0) {

                data.chk = true;

                data.cnt = lists.length;

                data.title = new Array();

                data.description = new Array();

                data.start = new Array();

                data.end = new Array();

                data.eventId = new Array();

                $.each(lists, function(i, item){

                    data.title[i] = item.summary;

                    data.description[i] = item.description;

                    data.start[i] = new Date(item.start.dateTime.value);

                    data.end[i] = new Date(item.end.dateTime.value);

                    data.eventId[i] = item.id;

                });

            }else {

                data.chk = false;

            }

        }

    });

}

// 주단위로 화면에 그린다

function screenWriteWeek() {

    var date = new Date();

    var month = date.getMonth()+1+locationMonth;

    if(month == 0) {

        locationYear--;

        locationMonth = 12 - Math.abs(locationMonth);

        month = date.getMonth()+1+locationMonth;

    }else if(month == 13) {

        locationYear++;

        locationMonth = locationMonth - 12;

        month = date.getMonth()+1+locationMonth;

    }

    var year = date.getFullYear()+locationYear;

    if(locationWeek < 0) {

        locationMonth--;

        month = date.getMonth()+1+locationMonth;

        if(month == 0) {

            locationYear--;

            locationMonth = 12 - Math.abs(locationMonth);

            month = date.getMonth()+1+locationMonth;

            year = date.getFullYear()+locationYear;

        }

        if(new Date(year, month-1, calendar.lastDay(year, month)).getDay() == 6) {

            locationWeek = calendar.make(year, month).length/7-1;

        }else {

            locationWeek = calendar.make(year, month).length/7-2;

        }

    }else if(locationWeek > calendar.make(year, month).length/7-2) {

        locationMonth++;

        month = date.getMonth()+1+locationMonth;

        if(month == 13) {

            locationYear++;

            locationMonth = locationMonth - 12;

            month = date.getMonth()+1+locationMonth;

            year = date.getFullYear()+locationYear;

        }        

        locationWeek = 0;

    }

    var months = [month-1, month, month+1];

    if(month == 1) {

        months = [12, month, month+1];

    }else if(month == 12) {

        months = [month-1, month, 1];

    }

    var monthDay = calendar.make(year, months[1]);

    var start = 0+locationWeek*7;

    var last = 6+locationWeek*7;

    var startIndex = monthDayIndex(monthDay, 1);

    var lastIndex = monthDayIndex(calendar.makeOne(year, months[1]), calendar.lastDay(year, months[1])) + startIndex;

    for(var i=start; i<=last; i++) {

        if(i<startIndex) {

            $('.week').eq(i%7).text('('+numFormat(months[0])+'.'+numFormat(monthDay[i])+')');

        }else if(i <= lastIndex) {

            $('.week').eq(i%7).text('('+numFormat(months[1])+'.'+numFormat(monthDay[i])+')');

        }else {

            $('.week').eq(i%7).text('('+numFormat(months[2])+'.'+numFormat(monthDay[i])+')');

        }

    }

    if(locationWeek != 0) {

        $('#yearMonth').text(year + "년 " + numFormat(months[1]) + "월 " + numFormat(monthDay[start]) + "일 ~ " + numFormat(months[1]) + "월 " + numFormat(monthDay[last]) + "일");

    }else {

        if(months[1== 1) {

            $('#yearMonth').text((year-1+ "년 " + numFormat(months[0]) + "월 " + numFormat(monthDay[start]) + "일 ~ " + year + "년 " + numFormat(months[1]) + "월 " + numFormat(monthDay[last]) + "일");

        }else {

            $('#yearMonth').text(year + "년 " + numFormat(months[0]) + "월 " + numFormat(monthDay[start]) + "일 ~ " + numFormat(months[1]) + "월 " + numFormat(monthDay[last]) + "일");

        }

    }

    var tag = new StringBuffer();

    for(var i=0, j=0, k=0; i<384; i++) {

        if(i%8 == 0) {

            tag.append('<tr style="text-align: center;">');

            j++;

        }

        if(j%2 == 1) {

            if(i%8 == 0) {

                tag.append('<td rowspan="2" style="height: 5px;></td>');

                tag.append('<td style="height: 5px; border-bottom: 1px dotted orange;">'+numFormat(k++)+':00</td>');

            }else {

                tag.append('<td class="'+j+'" style="height: 5px; border-bottom: 1px dotted orange;"></td>');

            }

        }else {

            if(i%8 < 7) {

                tag.append('<td class="'+j+'" style="height: 5px; border-top: 1px dotted orange;"></td>');

            }

        }

        if(i%8 == 7) {

            tag.append('</tr>');

        }

    }

    $('#tbody').html(tag.toString());

    // 캘린더 날짜정보 처리 고민필요 지금은 귀찮으니 대충 작성

    if(data.chk) {

        for(var i=start; i<=last; i++) {

            for(var j=0; j<data.cnt; j++) {

                if(i<startIndex) {

                    weekStartTimeAppend(i, j, monthDay, months, year, -1);

                    weekEndTimeAppend(i, j, monthDay, months, year, -1);

                }else if(i <= lastIndex) {

                    if(monthDay[i] == data.start[j].getDate() && months[1== data.start[j].getMonth()+1 && year == data.start[j].getFullYear()) {

                        if(data.start[j].getMinutes() < 30) {

                            $('.'+(data.start[j].getHours()*2+1)).eq(i%7).html(eventTagFormat(getTime(data.start[j]), data.title[j], data.eventId[j], data.description[j]));

                        }else {

                            $('.'+(data.start[j].getHours()*2+2)).eq(i%7).html(eventTagFormat(getTime(data.start[j]), data.title[j], data.eventId[j], data.description[j]));

                        }

                    }

                    if(data.start[j].getHours() != data.end[j].getHours() || (data.start[j].getMinutes() < 30 && data.end[j].getMinutes() >= 30)) {

                        if(monthDay[i] == data.end[j].getDate() && months[1== data.end[j].getMonth()+1 && year == data.end[j].getFullYear()) {

                            if(data.end[j].getMinutes() < 30) {

                                $('.'+(data.end[j].getHours()*2+1)).eq(i%7).html(eventTagFormatEnd(getTime(data.end[j]), data.title[j], data.eventId[j], data.description[j]));

                            }else {

                                $('.'+(data.end[j].getHours()*2+2)).eq(i%7).html(eventTagFormatEnd(getTime(data.end[j]), data.title[j], data.eventId[j], data.description[j]));

                            }

                        }                    

                    }

                }else {

                    weekStartTimeAppend(i, j, monthDay, months, year, 1);

                    weekEndTimeAppend(i, j, monthDay, months, year, 1);

                }

            }

        }

    }

}

// startTime Tag append

function weekStartTimeAppend(i, j, monthDay, months, year, num) {

    if(monthDay[i] == data.start[j].getDate() && months[0== data.start[j].getMonth()+1) {

        if(months[0== 12 && year+num == data.start[j].getFullYear()) {

            if(data.start[j].getMinutes() < 30) {

                $('.'+(data.start[j].getHours()*2+1)).eq(i%7).html(eventTagFormat(getTime(data.start[j]), data.title[j], data.eventId[j], data.description[j]));

            }else {

                $('.'+(data.start[j].getHours()*2+2)).eq(i%7).html(eventTagFormat(getTime(data.start[j]), data.title[j], data.eventId[j], data.description[j]));

            }

        }else if(year == data.start[j].getFullYear()) {

            if(data.start[j].getMinutes() < 30) {

                $('.'+(data.start[j].getHours()*2+1)).eq(i%7).html(eventTagFormat(getTime(data.start[j]), data.title[j], data.eventId[j], data.description[j]));

            }else {

                $('.'+(data.start[j].getHours()*2+2)).eq(i%7).html(eventTagFormat(getTime(data.start[j]), data.title[j], data.eventId[j], data.description[j]));

            }                        

        }

    }    

}

// endTime Tag append

function weekEndTimeAppend(i, j, monthDay, months, year, num) {

    if(data.start[j].getHours() != data.end[j].getHours() || (data.start[j].getMinutes() < 30 && data.end[j].getMinutes() >= 30)) {

        if(monthDay[i] == data.end[j].getDate() && months[2== data.end[j].getMonth()+1) {

            if(months[2== 1 && year+num == data.end[j].getFullYear()) {

                if(data.end[j].getMinutes() < 30) {

                    $('.'+(data.end[j].getHours()*2+1)).eq(i%7).html(eventTagFormatEnd(getTime(data.end[j]), data.title[j], data.eventId[j], data.description[j]));

                }else {

                    $('.'+(data.end[j].getHours()*2+2)).eq(i%7).html(eventTagFormatEnd(getTime(data.end[j]), data.title[j], data.eventId[j], data.description[j]));

                }

            }else if(year == data.end[j].getFullYear()) {

                if(data.end[j].getMinutes() < 30) {

                    $('.'+(data.end[j].getHours()*2+1)).eq(i%7).html(eventTagFormatEnd(getTime(data.end[j]), data.title[j], data.eventId[j], data.description[j]));

                }else {

                    $('.'+(data.end[j].getHours()*2+2)).eq(i%7).html(eventTagFormatEnd(getTime(data.end[j]), data.title[j], data.eventId[j], data.description[j]));

                }

            }

        }                    

    }    

}

//일정 종료 태그를 만들어 준다

function eventTagFormatEnd(time, title, eventId, description) {

    var tag = new StringBuffer();

    tag.append("<p>");

    tag.append('<a style="color: red;" data-toggle="collapse" data-target="#collapseExample'+eventId+'" aria-expanded="false" aria-controls="collapseExample" onclick="collapse(\''+eventId+'\')">');

    tag.append(time+"  "+title);

    tag.append('</a>');

    tag.append("</p>");

    return tag.toString();

}

// 이전 주 이동

function moveWeekPre() {

    locationWeek--;

    screenWriteWeek();

}

// 다음 주 이동

function moveWeekNext() {

    locationWeek++;

    screenWriteWeek();

}

//이전 달 주 이동

function moveFastWeekPre() {

    locationMonth--;

    screenWriteWeek();    

}

// 다음 달 주 이동

function moveFastWeekNext() {

    locationMonth++;

    screenWriteWeek();    

}

// 주단위로 바꾼다

function tabWeek() {

    $('#movePre').attr('onclick''moveWeekPre()');

    $('#moveNext').attr('onclick''moveWeekNext()');

    $('#moveFastPre').attr('onclick''moveFastWeekPre()');

    $('#moveFastNext').attr('onclick''moveFastWeekNext()');

    if($('#chk').val() != '1') {

        $('#thead tr:eq(0) td:eq(0)').attr('colspan''8');

        $('#thead tr:eq(1)').prepend('<td>시간</td>');

        $('#chk').val('1');

    }

    screenWriteWeek();

}

// 월단위로 바꾼다

function tabMonth() {

    $('#movePre').attr('onclick''moveMonthPre()');

    $('#moveNext').attr('onclick''moveMonthNext()');

    $('#moveFastPre').attr('onclick''moveFastMonthPre()');

    $('#moveFastNext').attr('onclick''moveFastMonthNext()');

    if($('#chk').val() != '0') {

        $('#thead tr:eq(0) td:eq(0)').attr('colspan''7');

        $('#thead tr:eq(1) td:eq(0)').remove();

        $('.week').text('');

        $('#chk').val('0');

    }

    screenWriteMonth();

}

$(document).ready(function(){

    calendarEventList();

    screenWriteMonth();

});

Colored by Color Scripter

cs

 

 

 

 

4. 실행화면

캘린더 리스트 화면

 

 

캘린더 생성시 modal처리 화면

 

 

 

캘린더 수정시 modal처리 화면

 

 

 

캘린더 삭제시 sweetalert처리 화면

 

 

일정 월 처리화면

 

 

일정 주 처리화면

 

 

일정 상세보기 화면

 

 

일정등록 modal처리 화면

 

 

일정 수정처리 modal처리 화면

 

 

일정 삭제시 sweetalert처리 화면

 

 

일정 주 상세보기 화면

 

 

 

이상으로 google calendar API 구현이였는데요

저는 java로 구현했기때문에

만약 배포시 권한동의화면을 띄워야하는데 전혀 써먹지 못합니다.(java코드에서 브라우저를 실행시키기때문에 ㅋㅋ)

 

소감으로는 API를 쓰는데 생각보다 많은 노력이 필요하고 그 시간에 DB구축하고도 남아돌기 때문에 추천하지 않습니다.

만약 만든다면 배포할때를 위해 javaScript로 구현하시기 바랍니다.

참고 API문서

연동 : https://developers.google.com/google-apps/calendar/quickstart/js

CRUD 코드 : https://developers.google.com/google-apps/calendar/v3/reference/events/get

Data속성 : https://developers.google.com/google-apps/calendar/v3/reference/events

 

 

 GoogleCalendarAPI.zip 

 

 

출처 : https://handcoding.tistory.com/20

'Programing' 카테고리의 다른 글

그누보드 버전 확인 How to gnuboard CMS version check  (0) 2019.09.18
php 프로그래밍 게시판 사진 보이게 하는 소스코드  (0) 2016.03.27
알고리즘의 종류  (0) 2015.10.18
br 태그  (0) 2015.10.18
배치파일 명령어  (0) 2014.03.13

+ Recent posts