- 설계
- 백앤드
- 프론트앤드 (가이드 페이지)
- Log & Crash
- 프론트앤드 (관리자 페이지)
- 빌드 및 배포
- 시연
- 총 3주 진행
- 1주차: 기획 및 역할 분담
- 2주차: 구현
- 3주차: 버그 수정, 기능 보완
- 계정 명 - irteam
- WEB Server - nginx 1.4.0
- WAS Server - node v11.6.0
- npm 6.5.0
Mysql RDS
Nodejs Express
- VS Code
- Mysql Workbench
- 동적으로 관리 가능해야 한다.
- TOAST 서비스를 이용해야 한다.
- 샘플코드를 언어별로 사용자 편의에 맞게 보여주어야 한다.
- 이한결 - 프론트앤드 개발, 웹디자인, Log & Crash 적용
- 임기봉 - 인프라, 프론트앤드 개발 및 Deploy 적용
- 허진호 - 백앤드 개발 및 Notification 적용
- Log & Crash 서비스에서 Client IP를 확인하기 위해 http_realip_module을 설치
- NGINX 설치 폴더를 지정하여 효율적으로 관리
$sudo ./configure --with-http_ssl_module --with-debug --with-http_realip_module --prefix=/usr/local/nginx --user=irteam
Nginx | Apache | |
---|---|---|
요청 처리 방식 | Event Driven 이벤트가 발생할 때마다 비동기로 처리 |
MPM 하나의 요청 당 프로세스 혹은 쓰레드가 처리 |
- NGINX가 Apache보다 적은 수의 쓰레드를 사용하여, 메모리 사용량이 적음
* 홈(가이드 홈으로 리다이렉트)
* 가이드 페이지
* 관리자 페이지
-
- 구성 - JAVA Servlet + MVC 패턴 + 라이브러리
- 자유도가 낮지만 안정성은 높다.
- 스크립트 언어에 비해 배포가 까다롭다(소스코드 복사 or war 파일 )
- 많은 NHN 서비스가 사용 중이기 때문에 추가적인 관리 요소 감소
-
- 구성 - Javascript + V8 엔진 + 오픈소스 프레임웍 및 라이브러리
- Javascript 특성상 자유도 높다.
- 프론트앤드와 백앤드를 같은 개발 언어로 개발
-
- 구성 - Python + MVC 패턴 + ORM + 라이브러리
- DB 관리도 장고 소스코드 프로젝트에서 관리
- Nodejs와 비교하여 Java로 웹 개발을 하는 것이 미숙함
- 프론트앤드와 백앤드를 같은 언어로 개발할 수 있다는 장점
- html과 거의 같은 구조에 동적으로 data를 용이하게 사용할 수 있는 ejs 형식을 쉽게 적용할 수 있다는 장점
- 필요한 데이터 형식이 Part, Service, Content로 정형화되어있고 해당 요소 모두 필요한 컬럼들이 정해져 있음
- 서비스마다 달라지는 Content 내용에 경우 파일로 저장하기 때문에 DB에서 동적으로 바뀌는 요소들이 없음.
type | 내용 | |
---|---|---|
id | int | User ID |
type | char | User 타입(admin, client) |
char | User ID, email | |
password | char | User password |
name | char | User 이름 |
phone | char | User 휴대폰번호 |
code | char | 비밀번호 찾기시 참조할 코드 |
col | type | 내용 |
---|---|---|
id | int | Part ID |
name | char | Part 이름 |
col | type | 내용 |
---|---|---|
id | int | Service ID |
name | char | Service 이름 |
part | int | 해당 서비스가 속해 있는 Part ID(Foriegn Key 지정) |
col | type | 내용 |
---|---|---|
id | int | Content가 ID |
name | char | Content가 이름 |
service | int | 해당 Content가 속해 있는 Service ID(Foriegn Key 지정) |
- Nodejs 연동시 자동 생성
col | type | 내용 |
---|---|---|
session_id | char | 세션 ID |
expires | int | 세션 만료 시간 |
data | text | 세션 Data |
- 파일이나 github 에 특정 형식(reStructuredText, Markdown)으로 작성하면 그것을 .html 로 변환해서 hosting
- Python pip로 제공
- 마크다운으로 작성된 String data를 html 형식으로 변환
- Extension을 이용하여 Custom Extension을 이용하여 나만의 마크다운 문법을 만들 수 있음
- npm으로 제공
- npm으로 제공되기 때문에 Nodejs 연동 용이
- Custom Extension으로 나만의 마크다운 문법 생성 가능
- Logstash의 메모리 부담을 줄여줌
- Filebeat가 로그 데이터를 수집, 정제하고, Logstash가 Log&Crash로 전달하는 역할
- 구성 및 이중화 후, logstash, Log&Crash와 정상적인 연동까지 확인
- 보안 침해로 인한 인스턴스 삭제 조치 -> 잘못된 경로의 다운로드가 원인인 것으로 추정
"body-parser": "^1.18.3",
"cookie-parser": "~1.4.3",
"debug": "~2.6.9",
"ejs": "~2.5.7",
"express": "~4.16.0",
"express-mysql-session": "^2.1.0",
"express-session": "^1.15.6",
"http-errors": "~1.6.2",
"morgan": "~1.9.0",
"multer": "^1.4.1",
"mysql": "^2.16.0",
"request": "^2.88.0",
"showdown": "^1.9.0",
"showdown-prettify": "^1.3.0",
"showdown-table": "^1.0.1"
express - 웹 및 모바일 어플리케이션을 위한 웹 애플리케이션 프레임워크 body-parser - POST 처리를 위한 모듈 cookie-parser - http로 요청한 클라이언트 쿠키 정보에 접근하기 위한 모듈 ejs - html 대신 ejs를 사용하기 위한 모듈 multer - 파일 처리를 위한 모듈 morgan - http 리퀘스트에 대해 로깅하는 모듈 http-errors express에 에러를 생성하는 모듈 debug - express가 내부적으로 이 모듈을 사용하여 여러 정보를 로깅
express-session - 세션 처리를 위한 모듈 express-mysql-session - 세션 정보를 mysql db에 저장하기 위한 모듈
request 모듈 - 비밀번호 찾기 서비스에서 Notification 사용을 위한 모듈
showdown 모듈 - 마크다운으로 작성된 텍스트를 HTML 형식으로 변환
var converter = new showdown.Converter({ tables: true }), // 마크다운 표 적용 위한 옵션 값 추가
text = contentText,
html = converter.makeHtml(contentText);
외부에 함부로 노출되면 안되는 DB정보나 App Key 같은 정보는 conf 폴더에 따로 보관하여 Git에 추가하지 않음
module.exports = {
host : ' DB IP 주소',
user : 'DB 사용자 계정',
password : 'DB 비밀번호',
port : DB 사용 포트,
database : 'DB 이름',
multipleStatements : true // 다수의 쿼리 허용
};
module.exports = {
app_key : "***********" // Notification App Key
}
app.use('/', indexRouter);
app.use('/guide', guideRouter);
app.use('/admin', adminRouter);
- 로그인/로그아웃
- 비밀번호 찾기
- Admin 페이지 출력
- 파트/서비스 추가
- 파트/서비스 수정
- 파트/서비스 삭제
- 콘텐츠 추가
- 콘텐츠 수정
- 콘텐츠 삭제
- 메인 가이드 페이지 출력 및 데이터 가공
var session = require('express-session');
var MySQLStore = require('express-mysql-session')(session);
var options = require('../config/db.js'); // DB 설정 require
router.use(session({ // 세션 값 저장
secret: '12sdfwerwersdfserwerwef', // 랜덤값
resave: false,
saveUninitialized: true,
cookie: {
maxAge: 1000 * 60 * 60 // 쿠키 유효기간 1시간
}
}));
var sessionStore = new MySQLStore(options); // 세션 저장소로 MySQL 지정
router.post('/auth', formData.array('file'), function (req, res, next) {
var email = req.body.username;
function makeid() { // 랜덤한 8글자 생성
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < 8; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
//POST
var request = require('request');
// 헤더 부분
var headers = {
'Content-Type': 'application/json;charset=UTF-8'
}
var email_url = "https://api-mail.cloud.toast.com/email/v1.4/appKeys/"
email_url += app_key;
email_url += "/sender/auth-mail"
// 요청 세부 내용
var options = {
url: email_url,
method: 'POST',
headers: headers,
json: {
'senderAddress': 'jinho.huh@nhnent.com',
'templateId': 'authEmail',
'receiver': {
'receiveMailAddr': email,
'templateParameter': {
"auth_code": text
}
}
}
}
// 요청 시작 받은값은 body
request(options, function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body)
}
})
return text;
}
var code = makeid(); // 랜덤 코드 생성
var params = [code, email]
var sql = `UPDATE users SET code=? WHERE email=?`; // 생성한 코드 해당 사용자의 DB에 저장
conn.query(sql, params, function (err, results, fields) {
if (err) {
console.log(err);
} else {
res.render('auth', { 'email': email });
}
})
});
-
메인 주소(http://메인_주소)로 접속
- http://메인_주소/guide/TOAST/TOAST/overview 로 Redirect
-
- http://메인_주소/guide/:part/:service/:content
- 동적으로 part, service, content가 추가되어야 하기때문에 위와 같은 방식으로 구현
- 도메인의 part, service, content 내용을 참조하여 DB를 조회하고 파일을 읽음
// 가이드 페이지 목차 데이터 가공 및 전송
router.get('/:part/:service/:content', function (req, res, next) {
var part = req.params.part; // part 참조
var service = req.params.service; // service 참조
var content = req.params.content; // content 참조
var file_path = "./views/guide/" + part + "/" + service + "/" +content +".ejs" // 해당 파일 로드
var html_data = fs.readFileSync(file_path, 'utf8');
var sql = `SELECT *, name FROM parts;`; // 왼쪽 메뉴 바 구성을 위한 데이터 가공
sql += `SELECT * FROM services;`;
sql += `SELECT * FROM contents;`;
conn.query(sql, function (err, results, fields) {
if (err) {
console.log(err);
} else {
var data = [];
for (var i = 0; i < results[0].length; i++) {
var temp_part = {}
temp_part["id"] = results[0][i].id;
temp_part["name"] = results[0][i].name;
temp_part["service"] = [];
data[i] = temp_part;
}
service_list = [];
for (var i = 0; i < results[1].length; i++) {
var temp_service = {}
temp_service["id"] = results[1][i].id
temp_service["part"] = results[1][i].part
temp_service["name"] = results[1][i].name
temp_service["content"] = []
for(var j=0; j<results[2].length; j++){
if(results[2][j].service == temp_service["id"])
temp_service["content"].push(results[2][j].name)
}
service_list.push(temp_service)
}
for (var i = 0; i < service_list.length; i++) {
for(var j=0; j<data.length; j++){
if(data[j].id == service_list[i].part){
data[j].service.push(service_list[i])
}
}
}
console.log(data[0]);
res.render('menu', { "data" : data, "html_data" : html_data})
}
})
});
- 기본 showdown 모듈을 사용하게 되면 자동으로 코드를 변환하기 때문에 code를 언어별로 구성하기 힘들다.
- github 코드를 연동해서 불러오기 힘들다.
- 코드 복사 버튼을 추가하기 힘들다.
- showdown에서 사용자가 custom extension을 작성하면 사용자만의 마크다운 언어 생성 가능
# Custom 마크다운 문법
%github%{코드 이름}
{코드언어} {코드 github 주소}
%end%
- 코드 이름은 필수적으로 적어주어야함 - 다른 코드와 구분하기 위함
- 코드 하이라이트 언어는 185개 언어 지원 - https://highlightjs.org
- github 주소는 https://cdn.jsdelivr.net/gh/{GitUser}/{GitRepo}/{GitPath} - git file을 raw하게 가져오기 위함
- extension 적용 코드
var showdown = require('showdown')
showdown.extension('codeButtonBar', function () {
var matches = [];
var result = '<button name="';
return [
{
type: 'lang',
regex: /%github%([^]+?)%end%/gi, // Custom 마크다운
replace: function (s, match) {
console.log(match)
matches.push(match);
var init = ""
var codeList = matches[0].split("\n");
codeList.pop();
var codeName = codeList[0];
for (var i = 1; i < codeList.length; i++) {
var value = codeList[i].split(" ")
var lang = value[0];
var gitURL = value[1];
init += result;
init += lang;
init += '" onclick="getGithubCode(this,\''
init += codeName
init += '\')" value="'
init += gitURL;
init += '">';
init += lang;
init += '</button>'
init += ''
}
init += '<button name="'
init += codeName
init += '" onclick="copyToClipboard(this)">Copy</button>'
init += '<textarea style="display:none" id="'
init += "temp_" + codeName
init += '" class=""></textarea>'
init += '<pre><code id="'
init += codeName
init += '" name="codeArea"></code></pre>'
matches = []; //reset array
return init;
}
}
];
});
- 가이드 문서를 수정하고 싶을 때 가이드 문서 데이터는 파일로 구성
- 컨텐츠 양도 많고 모든 파일을 읽어서 데이터를 전송하기 힘들다.
- HTML에서 조회 버튼을 눌렀을 때 한번 더 서버에 해당 컨텐츠에 대한 요청을 전송
// 컨텐츠 수정시 해당 마크다운 데이터 전송
router.post('/sendContent', formData.array('file'), function (req, res, next) {
var partName = req.body.partName;
var serviceName = req.body.serviceName;
var contentName = req.body.contentName;
console.log(partName);
console.log(serviceName);
console.log(contentName);
var file_path = "./views/guide/" + partName + "/" + serviceName + "/" + contentName + ".md"
var html_data = fs.readFileSync(file_path, 'utf8');
res.send(html_data)
});
-
- 현재 동적으로 추가된 md파일과 ejs파일을 서버 내에 저장하는 구조
- 서버 2개를 돌리게 되면 가이드 컨텐츠 추가를 작동한 서버 1개에만 저장
-
- Object Storage를 연동하여 서버 모두 Object Storage에 해당 파일을 저장
- 두 서버 모두 컨텐츠를 참조할 때 Object Storage를 참조
-
- 현재 수정 기능은 단순 이름 변경에 불과
- 서비스 이동이나 계층적으로 확인 불가
-
- DB에서 정의된 종속 개념을 이용하여 트리형식으로 변환하여 출력
- 트리 형식으로 출력된 데이터를 참조하여 수정
-
- 현재 각 라우터마다 DB나 multer 등 각각 선언되어 있음
-
- module.export를 이용하여 더 모듈화하여 코드 단순화 필요
<div class="menu__wrap">
<ul data-menu="main" class="menu__level">
<% for (var i=0; i< data.length ; i++){ %>
<li class="menu__item"><a class="menu__link" data-submenu="part-<%= data[i].name%>" href="#"> //상위 메뉴
<%= data[i].name%></a></li>
<% } %>
</ul>
<% for (var i=0; i< data.length ; i++){ %>
<!-- Submenu 1 -->
<ul data-menu="part-<%= data[i].name%>" class="menu__level">
<% for (var j=0; j< data[i].service.length ; j++){ %>
<li class="menu__item"><a class="menu__link" data-submenu="service-<%= data[i].service[j].id%>" href="#"> //중간 메뉴
<%= data[i].service[j].name%></a></li>
<% } %>
</ul>
<% } %>
<% for (var i=0; i< data.length ; i++){ %>
<% for (var j=0; j< data[i].service.length ; j++){ %>
<!-- Submenu 1-1 -->
<ul data-menu="service-<%= data[i].service[j].id%>" class="menu__level">
<% for (var k=0; k< data[i].service[j].content.length ; k++){ %>
<li class="menu__item"><a class="menu__link" href="/guide/<%= data[i].name%>/<%= data[i].service[j].name%>/<%= data[i].service[j].content[k]%>"> //하위 메뉴
<%= data[i].service[j].content[k]%></a></li>
<% } %>
</ul>
<% } %>
<% } %>
</div>
- 오픈소스 변경
- data의 length만큼 data.name 출력
- 각 data name을 가지고 submenu
- 해당 name의 service의 lenght만큼 data.service.name 출력
- 각 service id를 가지고 submenu 남김
- 해당 service의 content의 length만큼 data.service.content 출력
- href로 각 content 페이지의 url 지정
<div class="content" style="z-index: 1; position: relative;">
<%- html_data %>
</div>
backend에 저장된 html-data(해당 url 참조) 출력
function getGithubCode(obj, codeName) {
var xhr = new XMLHttpRequest();
var url = obj.getAttribute("value");
var lang = obj.getAttribute("name");
xhr.open("GET", url);
xhr.send(null);
xhr.onload = function () {
if (xhr.status === 200 || xhr.status === 201) {
console.log(xhr.responseText);
var copyName = "temp_" + codeName
document.getElementById(copyName).textContent = xhr.responseText; //copy 본
document.getElementById(codeName).textContent = xhr.responseText; //클라이언트에서 보여지는 것
document.getElementById(codeName).setAttribute("class", lang)
$(document).ready(function () {
$('pre code').each(function (i, block) {
hljs.highlightBlock(block);
});
});
} else {
console.error(xhr.responseText);
}
};
}
- admin.js에서 호출
- GET 요청으로 Github에 올라가 있는 SampleCode 출력
- 코드 하이라이트 기능(android studio format)
"<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.5.0/styles/androidstudio.min.css">"
"<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.5.0/highlight.min.js"></script>"
<script>hljs.initHighlightingOnLoad();</script>
- copyName은 copyToClipboard를 위함 (온전한 코드)
- codeName은 클라이언트에게 보여질 코드 -> set Attribute 과정 거치면서 변환
function copyToClipboard(element) {
var $temp = $("<textarea>");
var codeName = element.getAttribute("name");
$("body").append($temp);
console.log(document.getElementById("temp_" + codeName).textContent)
$temp.val(document.getElementById("temp_" + codeName).textContent).select();
document.execCommand("copy");
$temp.remove();
alert("복사되었습니다!");
};
- admin.js에서 호출
- 임시 숨겨진 텍스트 필드 temp 생성 -> element의 내용을 텍스트 필드에 복사 -> 텍스트필드의 내용 선택 -> 복사 명령 -> 임시 필드 텍스트 제거
- copyName = "temp_" + codeName 으로 얻어진 copyName의 데이터 복사
input {
beats{
port => 5044
}
}
filter {
if [source] == "/usr/local/nginx/logs/access.log"{
grok{
match => { "message" => "%{COMBINEDAPACHELOG}" }
}
mutate{
rename => {
"message"=>"body"
}
add_field => {
"projectName" => "5hS48AFbQVT0B8pz"
"projectVersion" => "1.0.0"
"logSource" => "http"
"logVersion" => "v2"
"logType" => "nginx-access"
}
}
}
else if [source] =="/usr/local/nginx/logs/error.log"{
grok{
match => { "message" => "%{YEAR}[./-]%{MONTHNUM}[./-]%{MONTHDAY}[- ]%{TIME} \[%{LOGLEVEL:severity}\] %{POSINT:pid}#%{NUMBER}: %{GREEDYDATA:errormessage}" }
}
mutate{
rename => {
"message"=>"body"
}
add_field => {
"projectName" => "5hS48AFbQVT0B8pz"
"projectVersion" => "1.0.0"
"logSource" => "http"
"logVersion" => "v2"
"logType" => "nginx-error"
}
}
}
}
output {
stdout { codec => rubydebug }
http {
url => "http://api-logncrash.cloud.toast.com/v2/log"
http_method => "post"
format => "json"
ssl_certificate_validation => false
}
}
- 각 웹서버의 filebeat에서 전송된 데이터를 5044 포트로 받음
- log의 source에 따라 access와 error 구분
- access 는 %{COMBINEDAPACHELOG}로 분류
- error 는 match로 포맷 맞춤
- add_field에 POST에 필요한 정보 입력(APPKey, projectVersion 등)
- http POST 방식으로 전송
- Log&Crash url 입력
- 기본적으로는 LBaaS의 IP만 나오므로 Client IP가 나오도록 설정
set_real_ip_from 192.168.0.17;
real_ip_header X-Forwarded-For;
- nginx 컴파일 설치 시에
--with-http_realip_module
같이 설치
- 로그인
verb:"POST" AND referrer:"\"http\:\/\/133.186.150.56\/login\"" AND response:"302"
- 로그인 timeout (에러)
source:"\/usr\/local\/nginx\/logs\/error.log" AND body:"Connection" AND body:"timed" AND body:"out" AND body:"login"
- 로그아웃
verb:"POST" AND body:"logout" AND response:"302"
- 컨텐츠 추가
referrer:"\"http\:\/\/133.186.150.56\/admin\/addContent\"" AND verb:"POST" AND response:"302"
- 컨텐츠 삭제
referrer:"\"http\:\/\/133.186.150.56\/admin\/deleteContent\"" AND verb:"POST" AND response:"302"
- Emerg 에러 : 서비스 불가 수준 (보통 Syntax 에러)
source:"\/usr\/local\/nginx\/logs\/error.log" AND severity:"emerg"
- 404 에러 (요청한 자료 없음)
verb:"GET" AND response:"404"
- 503 에러 (WAS 서버 문제)
verb:"GET" AND response:"503"
- $request_time : 해당 요청을 처리하는 데 걸린 응답시간
- $connection : nginx에 붙은 connection 갯수.
=> Log&Crash 알람 설정에 넣으면, 접속 지연 및 접속량 확인하기 좋을 것
/usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs/log/logs.log
=> json 형태
- logstash-filter-json plugin 설치 및 활용
input {
beats{
port => 5044
}
}
filter {
json{
target => "data"
add_field => {
"projectName" => "5I7w3dtIXvLdBB9E"
"projectVersion" => "1.0.0"
"logSource" => "nodejs"
"logVersion" => "v2"
"logType" => "nodejs-log"
}
}
}
output {
stdout { codec => rubydebug }
http {
url => "http://api-logncrash.cloud.toast.com/v2/log"
http_method => "post"
format => "json"
ssl_certificate_validation => false
}
}
{
"message":"::ffff:192.168.0.13 - - [15/Jan/2019:10:33:23 +0000] \"GET /admin HTTP/1.0\" 200 4495 \"http://133.186.150.56/login\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36\"\n",
"level":"info"
}
- log가 웹서버와 흡사
- 의미가 없다고 판단
- 구현 중지
- 선택한 파트 별로 서비스 리스트가 바뀌도록 구현
obj.options[obj.options.selectedIndex].getAttribute("id")
로 선택된 selectbox의 값을 추출
for (var i=0; i<serviceList.length; i++){
if (selectItem == serviceList[i].part){
var temp = {}
temp["name"]= serviceList[i].name;
temp["part"]= serviceList[i].part;
temp["value"]= serviceList[i].value;
changeItem.push(temp)
}
}
- jQuery 활용
$("#selectService").empty();
var option = $("<option value=''> 서비스 선택 </option>") // Set Default Value of Option
$('#selectService').append(option);
for (var i=0;i<changeItem.length;i++){
var option = $("<option name=" + changeItem[i].part + " value=" + changeItem[i].value + ">"+changeItem[i].name+"</option>");
$('#selectService').append(option);
}
- 선택한 파트 별로 서비스 리스트가 바뀌도록 구현
obj.options[obj.options.selectedIndex].getAttribute("id")
로 선택된 selectbox의 값을 추출
for (var i=0; i<serviceList.length; i++){
if (selectItem == serviceList[i].part){
var temp = {}
temp["name"]= serviceList[i].name;
temp["part"]= serviceList[i].part;
temp["value"]= serviceList[i].value;
changeItem.push(temp)
}
}
- jQuery 활용
$("#selectService").empty();
var option = $("<option value=''> 서비스 선택 </option>") // Set Default Value of Option
$('#selectService').append(option);
for (var i=0;i<changeItem.length;i++){
var option = $("<option name=" + changeItem[i].part + " value=" + changeItem[i].value + ">"+changeItem[i].name+"</option>");
$('#selectService').append(option);
}
var simplemde = new SimpleMDE({
tabSize:4,
element: document.getElementById("contentText")
});
- 선택한 파트 별로 서비스 리스트가 바뀌도록 구현
obj.options[obj.options.selectedIndex].getAttribute("id")
로 선택된 selectbox의 값을 추출
for (var i=0; i<serviceList.length; i++){
if (selectItem == serviceList[i].part){
var temp = {}
temp["name"]= serviceList[i].name;
temp["part"]= serviceList[i].part;
temp["value"]= serviceList[i].value;
changeItem.push(temp)
}
}
- jQuery 활용
$("#selectService").empty();
var option = $("<option value=''> 서비스 선택 </option>") // Set Default Value of Option
$('#selectService').append(option);
for (var i=0;i<changeItem.length;i++){
var option = $("<option name=" + changeItem[i].part + " value=" + changeItem[i].value + ">"+changeItem[i].name+"</option>");
$('#selectService').append(option);
}
1) 최소 Dependency로 활용할 수 있는 Editor
2) 작성한 문서 미리보기를 제공하는 Editor
- Dependencies
<link rel="stylesheet" href="http://lab.lepture.com/editor/editor.css" />
<script type="text/javascript" src="http://lab.lepture.com/editor/editor.js"></script>
<script type="text/javascript" src="http://lab.lepture.com/editor/marked.js"></script>
- 미리보기 버튼을 눌러야만 작성된 문서의 미리보기를 볼 수 있고, 편집 창을 반으로 나눠서 실시간으로 보는 기능을 제공하지 않음
- 폰트 크기가 작고, 대제목과 소제목 별로 구분되지 않음
- Dependencies
<link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
<script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script>
- 작성한 문서 미리보기를 제공, 편집 창을 반으로 나눠서 실시간으로 보는 기능을 제공
- Editor 보다 폰트 크기가 크고, 대제목과 소제목 별로 크기가 달라 구분이 용이
- 선택한 파트 별로 서비스 리스트가 바뀌도록 구현
obj.options[obj.options.selectedIndex].getAttribute("id")
로 선택된 selectbox의 값을 추출
for (var i=0; i<serviceList.length; i++){
if (selectItem == serviceList[i].part){
var temp = {}
temp["name"]= serviceList[i].name;
temp["part"]= serviceList[i].part;
temp["value"]= serviceList[i].value;
changeItem.push(temp)
}
}
- jQuery 활용
$("#selectService").empty();
var option = $("<option value=''> 서비스 선택 </option>") // Set Default Value of Option
$('#selectService').append(option);
for (var i=0;i<changeItem.length;i++){
var option = $("<option name=" + changeItem[i].part + " value=" + changeItem[i].value + ">"+changeItem[i].name+"</option>");
$('#selectService').append(option);
}
- 선택한 파트 별로 서비스 리스트가 바뀌도록 구현
obj.options[obj.options.selectedIndex].getAttribute("value")
로 선택된 selectbox의 값을 추출
for (var i=0; i<serviceList.length; i++){
if (selectItem == serviceList[i].part){
var temp = {}
temp["name"]= serviceList[i].name;
temp["part"]= serviceList[i].part;
temp["value"]= serviceList[i].value;
changeItem.push(temp)
}
}
- jQuery 활용
$("#selectContent").empty();
var option = $("<option value=''> 콘텐츠 선택 </option>") // Set Default Value of Option
$('#selectContent').append(option);
for (var i = 0; i < changeItem.length; i++) {
var option = $("<option name=" + changeItem[i].service + ' value="' + changeItem[i].value + '">' + changeItem[i].name + "</option>");
$('#selectContent').append(option);
}
- 이미 만든 콘텐츠의 내용을 불러오는 함수
- XMLHttpRequest 활용
- 페이지 Reload 없이 특정 내용만 업데이트 가능
- 페이지가 로드된 후 데이터 요청 / 수신
xhr.onload = function () {
$('#contentText').val(xhr.responseText);
$('#changeName').val(content);
$codemirror.getDoc().setValue(xhr.responseText);
};
xhr.open("POST", "/admin/sendContent");
xhr.send(formData); // 데이터를 stringify해서 보냄
- FormData 활용
- form 필드와 그 값을 나타내는 일련의 key/value 쌍을 쉽게 생성
- XMLHttpRequest.send()를 사용하여 쉽게 전송
formData.append('partName', part);
formData.append('serviceName', service);
formData.append('contentName', content);
- 선택한 파트 별로 서비스 리스트가 바뀌도록 구현
obj.options[obj.options.selectedIndex].getAttribute("id")
로 선택된 selectbox의 값을 추출
for (var i=0; i<serviceList.length; i++){
if (selectItem == serviceList[i].part){
var temp = {}
temp["name"]= serviceList[i].name;
temp["part"]= serviceList[i].part;
temp["value"]= serviceList[i].value;
changeItem.push(temp)
}
}
- jQuery 활용
$("#selectService").empty();
var option = $("<option value=''> 서비스 선택 </option>") // Set Default Value of Option
$('#selectService').append(option);
for (var i=0;i<changeItem.length;i++){
var option = $("<option name=" + changeItem[i].part + " value=" + changeItem[i].value + ">"+changeItem[i].name+"</option>");
$('#selectService').append(option);
}
- 선택한 서비스 별로 콘텐츠 리스트가 바뀌도록 구현
obj.options[obj.options.selectedIndex].getAttribute("value")
로 선택된 selectbox의 값을 추출
for (var i=0; i<serviceList.length; i++){
if (selectItem == serviceList[i].part){
var temp = {}
temp["name"]= serviceList[i].name;
temp["part"]= serviceList[i].part;
temp["value"]= serviceList[i].value;
changeItem.push(temp)
}
}
- jQuery 활용
$("#selectContent").empty();
var option = $("<option value=''> 콘텐츠 선택 </option>") // Set Default Value of Option
$('#selectContent').append(option);
for (var i = 0; i < changeItem.length; i++) {
var option = $("<option name=" + changeItem[i].service + ' value="' + changeItem[i].value + '">' + changeItem[i].name + "</option>");
$('#selectContent').append(option);
}
- 파트 및 서비스 추가 및 수정, 삭제는 하나의 페이지에 구성해도 무방함
- 페이지 별 중복되는 jQuery Method의 모듈화
- 파트 및 서비스, 콘텐츠 삭제 성공 시 팝업되는 Alert 창 → Confirm 창으로 변경하여 실수로 데이터 삭제하는 오류 방지
- 설치
$sudo npm install forever -g //global 설치하여 새로운 배포가 이루어져도 모듈 재설치를 방지
- Node.js Web Application 구동
$forever start -c "npm start" ./ //현재 프로젝트 폴더에서 npm start 명령어 수행. -c 옵션 추가되지 않을 시 default value : node
- Node.js Web Application 정지
$forever stopall //모든 구동중인 Node.js Web Application 중지
- PUSH
$git add .
$git commit -m "MSG"
$git tag TAG_NAME
$git push REPOSITORY_URL TAG_NAME
- Rollback 수행
$sudo git clone GIT_REPOSITORY_URL
$sudo git checkout TAG_NAME
- #1 WAS Server
cd /usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs
#stop old application
forever stopall
#backup contents
sudo cp -r /usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs/views/guide /home/irteam
#delete old application
cd ..
sudo mv TOASTGuidePage_TOAST_Nodejs TOASTGuidePage_TOAST_Nodejs.old
sudo rm -rf TOASTGuidePage_TOAST_Nodejs.old
#clone new application
sudo git clone https://github.com/jh8579/TOASTGuidePage_TOAST_Nodejs.git
- #1 WEB Server
cd /usr/local/nginx
#stop Nginx server
sudo ./sbin/nginx -s stop
- #1 WAS Server
cd /usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs
#import db & email.js file & content file
sudo mkdir config
cd config
sudo cp /home/irteam/db.js ./
sudo cp /home/irteam/email.js ./
# remove previous guide page
cd ..
sudo rm -rf /usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs/views/guide/
#import backuped contents
sudo cp -r /home/irteam/guide /usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs/views
#change permissions to write contents
sudo chown -R irteam:irteam /usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs
sudo chown irteam:irteam /usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs/*
sudo chown -R irteam:irteam /usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs/views/guide/
#instal npm & forever module
sudo ../npm install
#start new application
forever start -c "npm start" ./
- #1 WEB Server
cd /usr/local/nginx
#start Nginx server
sudo ./sbin/nginx
- #1 Build&Deploy 과정과 동일
- #1 WAS Server
cd /usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs
#stop old application
forever stopall
#backup contents
sudo cp -r /usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs/views/guide /home/irteam
#delete old application
cd ..
sudo mv TOASTGuidePage_TOAST_Nodejs TOASTGuidePage_TOAST_Nodejs.old
sudo rm -rf TOASTGuidePage_TOAST_Nodejs.old
#clone new application
sudo git clone https://github.com/jh8579/TOASTGuidePage_TOAST_Nodejs.git
- #1 WEB Server
cd /usr/local/nginx
#stop Nginx server
sudo ./sbin/nginx -s stop
- #1 WAS Server
cd /usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs
sudo git checkout v4.0
#import db & email.js file & content file
sudo mkdir config
cd config
sudo cp /home/irteam/db.js ./
sudo cp /home/irteam/email.js ./
# remove previous guide page
cd ..
sudo rm -rf /usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs/views/guide/
#import backuped contents
sudo cp -r /home/irteam/guide /usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs/views
#change permissions to write contents
sudo chown -R irteam:irteam /usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs
sudo chown irteam:irteam /usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs/*
sudo chown -R irteam:irteam /usr/local/nodejs/bin/TOASTGuidePage_TOAST_Nodejs/views/guide/
#instal npm & forever module
sudo ../npm install
#start new application
forever start -c "npm start" ./
- #1 WEB Server
cd /usr/local/nginx
#start Nginx server
sudo ./sbin/nginx
- #1 Rollback 과정과 동일
홈 - http://loaclhost:3000 가이드 - http://localhost:3000/admin
- 접속
- 로그인
- 파트 추가/서비스 추가
- 가이드 확인
- 파트/서비스 수정
- 가이드 확인
- 파트/서비스 삭제
- 가이드 확인
- 컨텐츠 추가
- 가이드 확인
- 컨텐츠 수정
- 가이드 확인
- 컨텐츠 삭제
- 가이드 확인
- 비밀번호 찾기
- 이메일 확인
- 바꾼 패스워드로 로그인
# 샘플코드 가이드
## 샘플코드 1
%github%samplecode1
java https://cdn.jsdelivr.net/gh/jh8579/SampleCode_TOAST-Image_Java/src/serviceSampleCode/UploadAPI.java
python https://cdn.jsdelivr.net/gh/jh8579/SampleCode_TOAST-Image_Python/upload_api.py
php https://cdn.jsdelivr.net/gh/jh8579/SampleCode_TOAST-Image_PHP/uploadAPI.php
%end%
## 샘플코드 2
%github%samplecode2
java https://cdn.jsdelivr.net/gh/jh8579/SampleCode_TOAST-Image_Java/src/serviceSampleCode/DeleteAPI.java
php https://cdn.jsdelivr.net/gh/jh8579/SampleCode_TOAST-Image_PHP/deleteAPI.php
%end%