mycoms
02-28
240228김하영 커밋
@ac81ffc7749174980fa6b71acb6bce440e31a6c1
+++ package-lock.json
This file is too big to display. |
+++ package.json
... | ... | @@ -0,0 +1,32 @@ |
1 | +{ | |
2 | + "dependencies": { | |
3 | + "@babel/cli": "^7.22.10", | |
4 | + "@babel/core": "^7.22.10", | |
5 | + "animate.css": "^4.1.1", | |
6 | + "axios": "^1.4.0", | |
7 | + "babel-loader": "^9.1.3", | |
8 | + "bootstrap": "^5.3.3", | |
9 | + "css-loader": "^6.8.1", | |
10 | + "express": "^4.18.2", | |
11 | + "express-http-proxy": "^1.6.3", | |
12 | + "file-loader": "^6.2.0", | |
13 | + "fs": "^0.0.1-security", | |
14 | + "new-line": "^1.1.1", | |
15 | + "vue": "^3.3.4", | |
16 | + "vue-loader": "^17.2.2", | |
17 | + "vue-router": "^4.2.4", | |
18 | + "vue-style-loader": "^4.1.3", | |
19 | + "webpack": "^5.88.2", | |
20 | + "webpack-cli": "^5.1.4" | |
21 | + }, | |
22 | + "scripts": { | |
23 | + "prod": "set NODE_ENV=production&&node ./server/modules/web/Server.js", | |
24 | + "dev": "set NODE_ENV=development&&node ./server/modules/web/Server.js", | |
25 | + "windows-prod": "set NODE_ENV=production&&node ./server/modules/web/Server.js", | |
26 | + "windows-dev": "set NODE_ENV=development&&node ./server/modules/web/Server.js", | |
27 | + "linux-prod": "export NODE_ENV=production&&node ./server/modules/web/Server.js", | |
28 | + "linux-dev": "export NODE_ENV=development&&node ./server/modules/web/Server.js", | |
29 | + "webpack-build": "webpack", | |
30 | + "webpack-build-watch": "webpack --watch" | |
31 | + } | |
32 | +} |
+++ server/modules/log/Logger.js
... | ... | @@ -0,0 +1,131 @@ |
1 | +const { LOG_BASE_DIR, SERVICE_STATUS } = require('../../../Global'); | |
2 | +const fs = require('fs'); | |
3 | +const Queue = require('../util/Queue'); | |
4 | + | |
5 | +/** | |
6 | + * @author : 하석형 | |
7 | + * @since : 2023.08.24 | |
8 | + * @dscription : Log 생성기 모듈 입니다. | |
9 | + */ | |
10 | +const Logger = (function () { | |
11 | + | |
12 | + /* let testInterval = setInterval(() => { | |
13 | + const date = new Date(); | |
14 | + var now = `${date.getFullYear()}.${(date.getMonth()+1)}.${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.${date.getMilliseconds()}`; | |
15 | + console.log('now :', now); | |
16 | + }, 1000) */ | |
17 | + | |
18 | + //로그 쓰기 전, 대기 저장소 | |
19 | + const eventQueue = new Queue(); | |
20 | + //로그 쓰는 중인지 아닌지 상태값 | |
21 | + let isLogging = false; | |
22 | + | |
23 | + /** | |
24 | + * @author : 하석형 | |
25 | + * @since : 2023.08.24 | |
26 | + * @dscription : Log 처리 | |
27 | + */ | |
28 | + const logging = (message) => { | |
29 | + const date = new Date(); | |
30 | + let year = date.getFullYear(); | |
31 | + let month = prefixZero((date.getMonth() + 1), 2); | |
32 | + let day = prefixZero(date.getDate(), 2); | |
33 | + let hour = prefixZero(date.getHours(), 2); | |
34 | + let minute = prefixZero(date.getMinutes(), 2); | |
35 | + let second = prefixZero(date.getSeconds(), 2); | |
36 | + let millisecond = prefixZero(date.getMilliseconds(), 3); | |
37 | + | |
38 | + //로그에 쓰일 정보 | |
39 | + const logMessage = { | |
40 | + message: message, | |
41 | + datetime: `${year}.${month}.${day} ${hour}:${minute}:${second}.${millisecond}`, | |
42 | + logFolderDir: `${LOG_BASE_DIR}/${year}${month}`,//log 폴더 경로 | |
43 | + logFileName: `log-${year}${month}${day}.log`//log 파일명 | |
44 | + } | |
45 | + | |
46 | + //로그 쓰는 중이면, 대기 저장소에 등록 | |
47 | + if (isLogging == true) { | |
48 | + eventQueue.push(logMessage); | |
49 | + } else {//로그 쓰는 중이 아니면, 로그 쓰는 중인 상태로 변경 후, 로그 쓰기 | |
50 | + isLogging = true; | |
51 | + | |
52 | + try { | |
53 | + //log 폴더 생성 | |
54 | + if (!fs.existsSync(logMessage.logFolderDir)) { | |
55 | + fs.mkdirSync(logMessage.logFolderDir, { recursive: true/*재귀적 폴더 생성*/ }); | |
56 | + } | |
57 | + | |
58 | + //log 파일 Full Path | |
59 | + let logFileFullPath = `${logMessage.logFolderDir}/${logMessage.logFileName}`; | |
60 | + //log 내용 | |
61 | + let logContent = `[${logMessage.datetime}] ${logMessage.message}`; | |
62 | + | |
63 | + //log 내용 쓰기 | |
64 | + writeLogFile(logFileFullPath, logContent); | |
65 | + } catch (error) { | |
66 | + console.log('logging error : ', error); | |
67 | + } finally { | |
68 | + isLogging = false; | |
69 | + } | |
70 | + } | |
71 | + } | |
72 | + | |
73 | + /** | |
74 | + * @author : 하석형 | |
75 | + * @since : 2023.08.24 | |
76 | + * @dscription : Log 내용 쓰기 | |
77 | + */ | |
78 | + const writeLogFile = (path, content) => { | |
79 | + if (SERVICE_STATUS == 'development') { | |
80 | + console.log(content); | |
81 | + } | |
82 | + | |
83 | + //파일 쓰기 | |
84 | + fs.appendFileSync(path, `${content}\n`, 'utf8'); | |
85 | + | |
86 | + //로그 쓰기 저장소에서 로그 메세지 꺼내기 | |
87 | + let logMessage = eventQueue.pop(); | |
88 | + //메세지가 존재하면 => Log 내용 쓰기 (재귀 호출) | |
89 | + if (logMessage != undefined) { | |
90 | + //log 파일 Full Path | |
91 | + let logFileFullPath = `${logMessage.logFolderDir}/${logMessage.logFileName}`; | |
92 | + //log 내용 | |
93 | + let logContent = `[${logMessage.datetime}] ${logMessage.message}`; | |
94 | + //Log 내용 쓰기 (재귀 호출) | |
95 | + writeLogFile(logFileFullPath, logContent); | |
96 | + } else { | |
97 | + return; | |
98 | + } | |
99 | + } | |
100 | + | |
101 | + /** | |
102 | + * @author : 하석형 | |
103 | + * @since : 2023.08.24 | |
104 | + * @dscription : 특정 길이만큼 앞에 '0' 붙이기 | |
105 | + */ | |
106 | + const prefixZero = (target, length) => { | |
107 | + let zero = ''; | |
108 | + let suffix = target; | |
109 | + let result = ''; | |
110 | + | |
111 | + if ((typeof target) === "number") { | |
112 | + suffix = target.toString(); | |
113 | + } | |
114 | + if (suffix.length < length) { | |
115 | + for (i = 0; i < length - suffix.length; i++) { | |
116 | + zero += '0'; | |
117 | + } | |
118 | + } | |
119 | + result = zero + suffix; | |
120 | + return result; | |
121 | + } | |
122 | + | |
123 | + | |
124 | + return { | |
125 | + logging: logging | |
126 | + } | |
127 | + | |
128 | +})(); | |
129 | + | |
130 | +//Module Export | |
131 | +module.exports = Logger;(파일 끝에 줄바꿈 문자 없음) |
+++ server/modules/util/Queue.js
... | ... | @@ -0,0 +1,32 @@ |
1 | +/** | |
2 | + * @author : 하석형 | |
3 | + * @since : 2023.08.24 | |
4 | + * @dscription : Queue(선입선출) 자료형 객체 입니다. | |
5 | + */ | |
6 | +class Queue { | |
7 | + constructor() { | |
8 | + this._arr = []; | |
9 | + } | |
10 | + | |
11 | + //입력 | |
12 | + push (item) { | |
13 | + this._arr.push(item); | |
14 | + } | |
15 | + | |
16 | + //출력 후, 제거 | |
17 | + pop () { | |
18 | + return this._arr.shift(); | |
19 | + } | |
20 | + | |
21 | + //출력 대기 중인 item return | |
22 | + peek () { | |
23 | + return this._arr[0]; | |
24 | + } | |
25 | + | |
26 | + //확인 | |
27 | + showQueue () { | |
28 | + console.log('Queue : ', this._arr); | |
29 | + } | |
30 | +} | |
31 | + | |
32 | +module.exports = Queue;(파일 끝에 줄바꿈 문자 없음) |
+++ server/modules/web/Server.js
... | ... | @@ -0,0 +1,105 @@ |
1 | +/** | |
2 | + * @author : 하석형 | |
3 | + * @since : 2023.08.24 | |
4 | + * @dscription : Express 라이브러리 활용 HTTP Web Server 모듈입니다. | |
5 | + */ | |
6 | +const { BASE_DIR, PORT, API_SERVER_HOST } = require('../../../Global'); | |
7 | +const Logger = require('../log/Logger');//Logger(필수) | |
8 | + | |
9 | +const express = require('express'); | |
10 | +const webServer = express(); | |
11 | +const expressProxy = require('express-http-proxy'); | |
12 | + | |
13 | +//파일 시스템 관련 라이브러리 | |
14 | +const FS = require('fs'); | |
15 | +//stream: 특정 자원을 Streaming 하기 위한 라이브러리 => Transform: Streaming 중인 자원의 Data에 Data 수정 및 추가를 지원해주는 객체 | |
16 | +const Transform = require('stream').Transform; | |
17 | +//Streaming 중인 자원에 새로운 데이터를 stream 공간에 추가하기 위한 라이브러리 | |
18 | +const newLineStream = require('new-line'); | |
19 | + | |
20 | +/** | |
21 | + * @author : 하석형 | |
22 | + * @since : 2023.08.24 | |
23 | + * @dscription : HTTP Server start | |
24 | + */ | |
25 | +webServer.listen(PORT, function () { | |
26 | + Logger.logging(`★★★ Node.js를 활용한 Web Server 구동(Port:${PORT}) ★★★`); | |
27 | +}) | |
28 | + | |
29 | +/** | |
30 | + * @author : 하석형 | |
31 | + * @since : 2023.08.24 | |
32 | + * @dscription : Intercepter 역할을 하는 미들웨어 기능 | |
33 | + */ | |
34 | +webServer.use(function (request, response, next) { | |
35 | + let ip = request.headers['x-forwarded-for'] || request.socket.remoteAddress; | |
36 | + Logger.logging(`[HTTP] ${request.url} (Method: ${request.method}, IP: ${ip})`); | |
37 | + next(); | |
38 | +}); | |
39 | + | |
40 | + | |
41 | + | |
42 | +/** | |
43 | + * @author : 하석형 | |
44 | + * @since : 2023.08.24 | |
45 | + * @dscription : ROOT URL -> index.html | |
46 | + */ | |
47 | +webServer.get('/', function (request, response) { | |
48 | + //response.sendFile을 통한 HTTP html reponse (html내용 Streaming) | |
49 | + response.sendFile(`${BASE_DIR}/client/views/index.html`); | |
50 | +}) | |
51 | + | |
52 | +/** | |
53 | + * @author : 하석형 | |
54 | + * @since : 2023.08.24 | |
55 | + * @dscription : 화면요청 URL 처리 | |
56 | + */ | |
57 | +webServer.get('*.page', function (request, response, next) { | |
58 | + //index.html 내용을 직접 Streaming하여 Response, Streaming 중간에 내용 수정 | |
59 | + //수정 내용 : URL 요청이 아닌, 브라우저에 표시된 URL만 변경하여, 해당하는 URL PATH의 Vue Component를 routing하기 위함 | |
60 | + const StreamTransform = new Transform(); | |
61 | + StreamTransform._transform = function (data, encoding, done) { | |
62 | + let fileContent = data.toString(); | |
63 | + let replaceBeforeContent = `<script id="app-start-vue-page">const APP_USER_HTTP_REQUEST_URL = '/';</script>`; | |
64 | + let replaceAfterContent = `<script id="app-start-vue-page">const APP_USER_HTTP_REQUEST_URL = '${request.params['0']}.page';</script>`; | |
65 | + fileContent.replace(replaceBeforeContent, replaceAfterContent); | |
66 | + this.push(fileContent); | |
67 | + done(); | |
68 | + } | |
69 | + //Streaming 진행 | |
70 | + FS.createReadStream(`${BASE_DIR}/client/views/index.html`).pipe(newLineStream()).pipe(StreamTransform).pipe(response); | |
71 | +}) | |
72 | + | |
73 | +/** | |
74 | + * @author : 하석형 | |
75 | + * @since : 2023.08.24 | |
76 | + * @dscription : REST API 서버에 데이터 요청 보내기(Proxy) | |
77 | + */ | |
78 | +webServer.use('*.json', expressProxy(API_SERVER_HOST, { | |
79 | + proxyReqPathResolver: function (request) { | |
80 | + //console.log('request : ', request.url, request.params[0]); | |
81 | + return `${request.params['0']}.json`; | |
82 | + } | |
83 | +})); | |
84 | + | |
85 | +/** | |
86 | + * @author : 하석형 | |
87 | + * @since : 2023.08.24 | |
88 | + * @dscription : ROOT URL, Router's, 화면요청 URL 등.. 이 외 나머지 정적 자원에 대한 처리 기능 | |
89 | + */ | |
90 | +webServer.get('*.*', function (request, response, next) { | |
91 | + response.sendFile(`${BASE_DIR}${request.params['0']}.${request.params['1']}`); | |
92 | +}) | |
93 | + | |
94 | +/** | |
95 | + * @author : 하석형 | |
96 | + * @since : 2023.08.24 | |
97 | + * @dscription : Global Error Handler (*맨 마지막에 적용해야됨) | |
98 | + */ | |
99 | +webServer.use(function (error, request, response, next) { | |
100 | + const errorCode = !error.statusCode ? 500 : error.statusCode; | |
101 | + response.status(errorCode).send('에러가 발생하였습니다. 관리자에게 문의바랍니다.'); | |
102 | + let message = `[Error:${errorCode}] ${request.url}/n ${error.stack}/n`; | |
103 | + Logger.logging(message); | |
104 | + //next(); | |
105 | +})(파일 끝에 줄바꿈 문자 없음) |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?