Skip to content

Commit

Permalink
[merge] merge all
Browse files Browse the repository at this point in the history
  • Loading branch information
OH-GITAEK committed May 28, 2024
2 parents 92db0da + c874311 commit bb6398e
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 279 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
# -
78 changes: 0 additions & 78 deletions api/cache/attendCaches.js

This file was deleted.

202 changes: 150 additions & 52 deletions api/cache/token.js
Original file line number Diff line number Diff line change
@@ -1,67 +1,165 @@
const { flushAttendCache } = require('./attendCaches');
const Attend = require('../models/attend');
// 동시성 문제를 해결하기위해 한 번에 하나의 비동기 작업만 해당 자원에 접근 가능
// 작업이 끝나기 전까지 다른 작업이 실행되지 않도록 보장
const { Mutex } = require('async-mutex');

const tokenCache = {
const TTL = 600000; // 10분 (600,000 밀리초)

// 출석에 대한 토큰이자 캐시 객체(서버 당 하나의 객체만 필요하므로 객체로 선언)
const AttendanceTokenCache = {
sessionId: null,
attendIdx: null,
expireAt: null,
attendIdx: null,
code: null,
};
const TTL = 600000; // 10분 (600,000 밀리초)
cacheTimeout: null,
attendCache: [],
mutex: new Mutex(),

// Token 삭제 함수
async function deleteToken(sessionId) {
await flushAttendCache(sessionId);
tokenCache.sessionId = null;
tokenCache.attendIdx = null;
tokenCache.expireAt = null;
tokenCache.code = null;
}

// 토큰 생성 함수
function createToken(sessionId, attendIdx) {
if (tokenCache.sessionId) {
console.log(`이미 출석 체크 중`);
return null; // 이미 존재하는 경우 false 반환
}
// 캐시 리셋 함수
resetCache() {
this.sessionId = null;
this.expireAt = null;
this.attendIdx = null;
this.code = null;
this.attendCache = [];

const expireAt = Date.now() + TTL;
const code = String(Math.floor(Math.random() * 10000)).padStart(4, '0');
tokenCache.sessionId = sessionId;
tokenCache.attendIdx = attendIdx;
tokenCache.expireAt = expireAt;
tokenCache.code = code;

// 시간 지나면 사라짐
setTimeout(async () => {
if (tokenCache.sessionId == sessionId && tokenCache.expireAt <= Date.now()) {
await deleteToken(sessionId);
if (this.cacheTimeout) {
clearTimeout(this.cacheTimeout);
this.cacheTimeout = null;
}
}, TTL);
},

return tokenCache; // 새로 설정된 경우 토큰 반환
}
// 캐시 삭제 후 데이터 베이스에 전달 함수
async flushCache() {
if (this.attendCache.length > 0) {
const bulkOps = this.attendCache.map(attend => ({
updateOne: {
filter: { user: attend.user, session: this.sessionId },
update: { $set: { attendList: attend.attendList } },
upsert: true
}
}));

// 토큰 재시작 함수
function restartToken(sessionId) {
if (tokenCache.sessionId !== sessionId) {
console.log(`세션 ID가 일치하지 않습니다.`);
return null; // 세션 ID가 일치하지 않는 경우 null 반환
}
try {
const result = await Attend.bulkWrite(bulkOps);
console.log('데이터베이스에 출석 정보 저장 성공:', result);
} catch (error) {
console.error('데이터베이스에 출석 정보 저장 실패:', error);
}
}

this.resetCache();
},

// 토큰 생성 후 토큰 반환 함수
async setToken(sessionId, attendIdx, attends) {
const release = await this.mutex.acquire(); // Mutex 잠금
try {
if (this.sessionId) {
console.log('이미 출석 체크 중입니다.');
return null;
}

this.expireAt = Date.now() + TTL;
this.code = String(Math.floor(Math.random() * 10000)).padStart(4, '0');
this.sessionId = sessionId;
this.attendIdx = attendIdx;
this.attendCache = [];

const token = tokenCache;
token.code = String(Math.floor(Math.random() * 10000)).padStart(4, '0');
token.expireAt = Date.now() + TTL;
// 매개변수로 attend배열을 받아옴
attends.forEach(attend => {
// attend중 idx가 맞은 거만 가져옴
let attendAtIdx = attend.attendList.find(item => item.attendIdx == attendIdx);
if (!attendAtIdx) { // 이게 없다면 생성 후 캐시에 저장
attendAtIdx = { attendIdx, status: false };
attend.attendList.push(attendAtIdx);
} else {
// 있다면 false로 바꿔
attendAtIdx.status = false;
}
// 캐시에 저장 (save를 하지 않으므로 디비에 영향 x)
this.attendCache.push(attend);
});

return token;
}
this.cacheTimeout = setTimeout(async () => {
if (this.expireAt <= Date.now()) {
await this.flushCache();
}
}, TTL);

// Token 조회 함수
function getToken(sessionId) {
if (tokenCache.sessionId == sessionId) {
return tokenCache;
} else {
return {
sessionId: this.sessionId,
attendIdx: this.attendIdx,
expireAt: this.expireAt,
code: this.code
};
// 임계 구역(이 위 코드는 이 작업이 끝난 이후에 다른 작업 가능)
} finally {
release();
}
},

// 토큰 재시작 후 토큰 반환 함수
async restartToken() {
const release = await this.mutex.acquire(); // Mutex 잠금
try {
if (!this.sessionId) {
console.log('토큰이 없습니다. 먼저 토큰을 생성하세요.');
return null;
}

this.code = String(Math.floor(Math.random() * 10000)).padStart(4, '0');
this.expireAt = Date.now() + TTL;

if (this.cacheTimeout) {
clearTimeout(this.cacheTimeout);
}

this.cacheTimeout = setTimeout(async () => {
if (this.expireAt <= Date.now()) {
await this.flushCache();
}
}, TTL);

this.attendCache.forEach(attend => {
const attendCheck = attend.attendList.find(item => item.attendIdx == this.attendIdx);
if (attendCheck) {
attendCheck.status = false;
} else {
attend.attendList.push({ attendIdx: this.attendIdx, status: false });
}
});

return {
sessionId: this.sessionId,
attendIdx: this.attendIdx,
expireAt: this.expireAt,
code: this.code
};

// 임계 구역(이 위 코드는 이 작업이 끝난 이후에 다른 작업 가능)
} finally {
release();
}
},

// 현재 토큰 정보 반환 함수
nowToken() {
if (this.sessionId) {
return {
sessionId: this.sessionId,
attendIdx: this.attendIdx,
expireAt: this.expireAt,
code: this.code
};
}
return null;
},

// 현재 출석 캐시 반환 함수
attendsCache() {
return this.attendCache;
}
}
};

module.exports = { createToken, getToken, restartToken, deleteToken };
module.exports = AttendanceTokenCache;
Loading

0 comments on commit bb6398e

Please sign in to comment.