runner-be / runnerbe-server Goto Github PK
View Code? Open in Web Editor NEW직장인 타겟 러닝 모임 O2O 플랫폼 🐝
License: Other
직장인 타겟 러닝 모임 O2O 플랫폼 🐝
License: Other
현재 3,7,9,10을 제외하면 현 상태에서 개발 가능할 것으로 보인다.
공식 문서를 따라 push alarm을 위한 기본 설정을 진행하고, 필요한 상황에 맞춰서 push alarm을 발송하는 메소드를 호출할 계획이다.
추가로 트랜잭션 처리도 함께 진행할 계획이다.
Data
query string 설계
필터 처리
A. 거리순 - 사용자(위도, 경도)[req.body]와 게시글 러닝 모임장소(위도, 경도) => km 환산 거리 계산 쿼리 작성
B. 최신순 - Posting createdAt 정렬
C. 찜많은순 - Bookmark table과 join
D. 마감 포함 여부 - query string , Running table과 join
Header : jwt
body : userLongitude, userLatitude
query string(현안- 1번): whetherEnd(Y, N), Filter_bookMark(Y, N), Filter_time(Y, N), Filter_distance(Y, N)
=> 좀 더 공부해보고 개선할 것.
GitHub Actions
AWS CodeDeploy
Test Code : Using Jest
방향
Date-based Scheduling로 진행
Say you very specifically want a function to execute at 5:30am on December 21, 2012. Remember - in JavaScript - 0 - January, 11 - December.
const schedule = require('node-schedule');
const date = new Date(2012, 11, 21, 5, 30, 0);
const job = schedule.scheduleJob(date, function(){
console.log('The world is going to end today.');
});
To use current data in the future you can use binding:
const schedule = require('node-schedule');
const date = new Date(2012, 11, 21, 5, 30, 0);
const x = 'Tada!';
const job = schedule.scheduleJob(date, function(y){
console.log(y);
}.bind(null,x));
x = 'Changing Data';
This will log 'Tada!' when the scheduled Job runs, rather than 'Changing Data', which x changes to immediately after scheduling.
활용
reference
https://www.npmjs.com/package/node-schedule
https://github.com/node-schedule/node-schedule
참석자 프로필 사진의 리스트를 넘겨야 하는데, 각 postId에 따라서 해당 리스트를 연결되도록 해야 한다.
ERD를 참고하면, postId로 해당 모임인 gatheringId를 가져온다. 이후 RunningPeople에서 맵핑되는 userId를 모두 가져와서 User 테이블과 join 후 프로필 사진 Url을 가져오도록 했다.
그 과정에서
select U.userId as userId, profileImageUrl from Posting
inner join Running R on Posting.postId = R.postId
inner join RunningPeople RP on R.gatheringId = RP.gatheringId
inner join User U on RP.userId = U.userId
where R.postId = ?;
해당 쿼리를 작성했는데, 일부 postId에서 빈 값으로 반환되는 경우가 있었다. 이는 맵핑되는 gatherId가 실제로 Running에 존재하지 않는 경우였다.
즉, 게시글 삭제 과정이나 생성 과정에서 DB 수정이 누락된 것이다.
53번 모임에 대해 신청한 사람들이 3명 있었는데, 나중에 해당 게시글을 삭제하였고, 이때 Running과 Posting 테이블에만 그 결과가 반영되고 RunningPeople은 그대로 남아 있는 것이다.
따라서 게시글 삭제 API에 문제가 있음을 추측할 수 있었다.
try {
//게시글 있는지 확인
const checkPostingResult = await postingProvider.checkPosting(postId);
if (checkPostingResult[0].length == 0)
return errResponse(baseResponse.POSTING_NOT_VALID_POSTID);
const connection = await pool.getConnection(async (conn) => conn);
// 게시글 수정
const dropPostingResult = await postingDao.dropPosting(connection, postId);
connection.release();
return response(baseResponse.SUCCESS);
} catch (err) {
logger.error(`App - dropPosting Service error\n: ${err.message}`);
return errResponse(baseResponse.DB_ERROR);
}
개발 당시 급하게 만들다 보니 단순히 Posting 테이블만 결과를 반영하였고, 이로 인해 문제가 발생한 것이다.
비록 배포 전 데이터이지만, 테스트 과정에서 혼돈이 생길 수 있기 때문에 오류 데이터들은 모두 삭제해 주었다.
각 테이블에 대해 삭제 쿼리를 구성하고 트랜잭션 처리를 하면 될 것 같다.
try {
let kakao_profile;
try {
kakao_profile = await axios.get("https://kapi.kakao.com/v2/user/me", {
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
} catch (err) {
return res.send(errResponse(baseResponse.ACCESS_TOKEN_IS_NOT_VALID)); // 2085
}
const uuid = kakao_profile.id;
-> kakao_profile is undefinded
여러 테이블에 걸쳐 특정 작업을 한 번에 진행되어야 하는 경우, 중간에 작업이 실패하게 되면 다시 rollback 해야 한다.
예시로, 게시글 삭제를 할 때 이전 이슈와 같이 Posting 테이블에서만 삭제할 것이 아니라 Running, RunningPeople 모두 삭제 작업을 한번에 진행해야 한다.
그래서 transaction을 적용하기로 하였고, 다음과 같이 구조를 새로 설계하였다.
// 게시글 삭제
exports.dropPosting = async function (postId) {
// 먼저 pool 생성해서 catch문에서 rollback하도록
const connection = await pool.getConnection(async (conn) => conn);
try {
//게시글 있는지 확인
const checkPostingResult = await postingProvider.checkPosting(postId);
if (checkPostingResult[0].length == 0)
return errResponse(baseResponse.POSTING_NOT_VALID_POSTID);
connection.beginTransaction(); // 트랜잭션 적용 시작
// 게시글 수정 - Posting, Running, RunningPeople
const dropPostingResult = await postingDao.dropPosting(connection, postId);
await connection.commit(); // 성공시 commit
return response(baseResponse.SUCCESS);
} catch (err) {
await connection.rollback(); // 실패시 rollback
logger.error(`App - dropPosting Service error\n: ${err.message}`);
return errResponse(baseResponse.DB_ERROR);
} finally {
connection.release();
}
};
기존에는 try 문 내에서 connection pool을 생성해서 connection을 가져왔고, 그 안에서 해제하였다.
하지만 transaction을 적용하기 위해서는, 에러 발생시 rollback을 시켜야 하고, 이를 위해서는 catch문 안에 rollback 메소드를 호출해야 한다.
따라서 위와 같이 connection pool을 블록 외부에서 생성하고, finally 문에서 해제하도록 한다.
그러면, 호출한 dao에서는 3가지 삭제 작업을 진행하게 될 것이고, 이때 어떤 이유에서 작업이 중단된다면, err response를 던질 것이다.
이때 catch 문 내 rollback 메소드가 호출되면서 DB는 다시 삭제되기 이전 상태로 돌아갈 것이다.
반대로, 아무 에러없이 잘 작동한다면 commit 메소드가 호출되면서 작업 결과가 DB에 반영될 것이다.
게시글 삭제를 비롯하여 모든 API에 대하여 트랜잭션 처리를 진행한다.
Describe the bug
{"level":"error","message":"User-getMain2Login Provider error: Cannot set properties of undefined (setting 'profileUrlList')","timestamp":"2022-07-29 01:57:06"}
Expected behavior
A clear and concise description of what you expected to happen.
Screenshots
If applicable, add screenshots to help explain your problem.
Desktop (please complete the following information):
Smartphone (please complete the following information):
Additional context
Add any other context about the problem here.
distanceFilter (0)
N : 설정 x -> 거리로 필터링 x , 기본 메인화면의 경우에 해당
value : 입력 받은 값(Km)만큼 거리로 필터링
성별 필터 genderFilter (0)
A : 전체, F : 여성, M : 남성
빈값확인, 유효성확인
연령대 필터
최소 연령대
최대 연령대
두 가지를 받고 검증 후 쿼리에 추가
직군필터
mysql INSTR 함수 사용
AND INSTR(J.job, ?) > 0
키워드 필터
INSTR 함수 사용
create definer = park@`%` event update_attendance on schedule
every '1' DAY
starts '2022-02-27 09:38:24'
enable
comment '출석률 업데이트'
do
UPDATE User U
INNER JOIN (SELECT userId, (SUM(attendance)/COUNT(gatheringId))*100 as attendance FROM RunningPeople GROUP BY userId) A
ON A.userId = U.userId
SET diligence = A.attendance
WHERE A.userId = U.userId;
create definer = park@`%` event update_whetherEnd on schedule
every '1' DAY
starts '2022-03-04 03:00:00'
enable
comment '마감 게시글 삭제 상태로 업데이트'
do UPDATE Running SET whetherEnd = 'D' WHERE whetherEnd = 'Y';
create definer = park@`%` event delete_posting on schedule
every '1' DAY
starts '2022-03-04 03:00:00'
enable
comment '마감된 게시글 비노출'
do
UPDATE Posting
SET status = 'D'
WHERE status = 'C';
글 작성자/ 비작성자 구분 필요
-> postId로 repUserId 갖고 와서 비교 후 response code로 구분하기
Data
Header : jwt
body :
path variable : postId, userId
query string :
code : 1003 = 성공, 작성자,
1004 = 성공, 비작성자
도메인이 적으므로, 역할 별로 묶어서 재구성
단, 토큰을 반환값으로 주는 것은 보안상 위험하므로 철회한다. (외부에서 해당 API로 userId 넣어서 jwt발급할 가능성)
소셜 로그인과 같은 복잡한 절차를 걸쳐야만 발급하도록 한다.
jwt의 유효기간이 1년으로 충분히 긴 기간동안 유지되므로, 만료됐다면 재로그인
회원 가입 API
-> 메일 인증인 경우는 쿼리 동일
-> 사진 인증의 경우 쿼리에서 status를 W로 변경하는 부분 수정
*추후 Admin API로 사진 인증 유저 관리
(User.status : W->Y)
로그인 API
uuid 확인
a. 없다 -> 회원가입 가능 응답
b. 있다 ->
*and status == Y -> 로그인 성공, jwt 발급
*and status == W -> 인증 대기중, 둘러보기(회원가입 요청 X)
내일 직접 개발해보고 내용 추가 예정
1.쪽지 목록
내가 속한 room 정보
그 room에서 수신자 인덱스를 상대방 id로 가져오고
상대방 id로 상대방 유저 정보 가져오기
요청하는 userId로 쿼리 조건문에서
WHERE senderId = ? or receiverId = ?
이렇게 하면 채팅방 정보와 별개로 내가 속한 모든 대화방 조회 가능
async function getMessageList(connection, userId) {
const query = `
SELECT R.roomId, title, counterId, nickName, profileImageUrl
FROM Room R
INNER JOIN Posting P on R.postId = P.postId
INNER JOIN (SELECT roomId, case when receiverId = ${userId} then senderId
when senderId = ${userId} then receiverId
end as counterId
FROM Room R
INNER JOIN Posting P on R.postId = P.postId
WHERE senderId = ${userId} or receiverId = ${userId}) D on D.roomId = R.roomId
INNER JOIN User U on U.userId = D.counterId
WHERE senderId = ${userId} or receiverId = ${userId};
`;
const row = await connection.query(query);
return row;
}
2.쪽지방 상세 페이지에서
myUserId = req.params.userId 전달받았을때
myUserId == 발신자 Id -> 내가 보낸 쪽지(왼쪽)
myUserId == 수신자 Id -> 상대방이 내게 보낸 쪽지(오른쪽)
내가 반장인지에 따라 result code 구분해서 응답 보내기 (다른 화면 구성)
읽음 처리하기 -> 쪽지 상세 페이지를 호출할 때 마다 room에 속한 모든 message의 읽음 여부를 Y로 처리하면,
쪽지를 마지막에 보낸 사람 입장에서는 현재 수신된 모든 쪽지를 읽었으니 ok
쪽지를 열어보는 사람 입장에서도 상세 페이지를 호출하면서 모든 쪽지를 읽음 처리
3.방 생성시 발신자, 수신자 인덱스 삽입
postId, jwt 주면
postId로 repUserId뽑아서 수신자로, 요청하는 userId 발신자로 삽입
이때 RunnungPeople에서 발신자의 userId가 있고 and whetherAccept가 D이면 신청 불가능
(게시글 상세페이지에서 참여 러너 수는 수락 여부가 Y인 경우만 가져오도록 조치함)
참여신청처리
roomId로 반장 Id 뽑아내고jwt로 검증
거절 : sendId에 맞는 RunningPeople 테이블에 참여 러너의 수락 여부를 D로 변경
수락 : "" 수락 여부를 Y로 변경
쪽지 보내기
수신자의 userId를 가져오는게 문제.
roomId와 senderId
해당 roomId로 sender와 receiver id를 차례로 비교하여, 일치하는 경우의 반대의 id를 가져와서 수신자 Id로 사용
일치하는 경우가 없다면 오류 뱉기
// receiverId 생성 절차
const senderParams = [roomId, senderId];
const checkEqualSender = await messageDao.checkSender(
connection,
senderParams
);
const checkEqualReceiver = await messageDao.checkReceiver(
connection,
senderParams
);
connection.release();
//반대로 전달
if (checkEqualSender.length > 0) {
const receiverId = await messageDao.getReceiver(connection, senderParams);
return receiverId;
} else if (checkEqualReceiver.length > 0) {
const receiverId = await messageDao.getSender(connection, senderParams);
return receiverId;
} else {
return errResponse(baseResponse.MESSAGE_NOT_MATCH_USERID);
}
Header : JWT
Path variable : postId, userId
Header : JWT
Path variable : userId
Header : JWT
Path variable : roomId, userId
Header : JWT
body : content
Path variable : roomId, userId
Header : JWT
Path variable : roomId, senderId
Describe the bug
Issue about connection pool (and maybe nginx load balancing)
To Reproduce
Steps to reproduce the behavior:
connection pool 설정을 수정하여 해결 시도
mysql2 npm document
declare namespace Pool {
export interface PoolOptions extends Connection.ConnectionOptions {
/**
* The milliseconds before a timeout occurs during the connection acquisition. This is slightly different from connectTimeout,
* because acquiring a pool connection does not always involve making a connection. (Default: 10 seconds)
*/
acquireTimeout?: number;
/**
* Determines the pool's action when no connections are available and the limit has been reached. If true, the pool will queue
* the connection request and call it when one becomes available. If false, the pool will immediately call back with an error.
* (Default: true)
*/
waitForConnections?: boolean;
/**
* The maximum number of connections to create at once. (Default: 10)
*/
connectionLimit?: number;
/**
* The maximum number of connection requests the pool will queue before returning an error from getConnection. If set to 0, there
* is no limit to the number of queued connection requests. (Default: 0)
*/
queueLimit?: number;
/**
* Enable keep-alive on the socket. It's disabled by default, but the
* user can enable it and supply an initial delay.
*/
enableKeepAlive?: true;
/**
* If keep-alive is enabled users can supply an initial delay.
*/
keepAliveInitialDelay?: number;
}
}
Screenshots
If applicable, add screenshots to help explain your problem.
Desktop (please complete the following information):
Smartphone (please complete the following information):
Additional context
Add any other context about the problem here.
connection 가져올 때 좀 더 유연한 정책을 적용할 예정
This issue provides visibility into Renovate updates and their statuses. Learn more
This repository currently has no open or pending branches.
사원증 인증으로 회원가입 했을 때, 관리자가 승인 완료 후 사용자가 어플에 최초 접속했을 때 "인증이 완료되었습니다"와 같은 안내 문구를 띄워야 함. 이를 위해서 사원증 인증 승인 이후 최초 접속한 경우에 대한 result code를 추가해야함
승인 이후 최초 접속한 것을 어떤 방식으로 구분해야 될 지 좀 더 고민해봐야함
Describe the bug
A clear and concise description of what the bug is.
`
node:internal/errors:465
ErrorCaptureStackTrace(err);
^
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:372:5)
at ServerResponse.setHeader (node:_http_outgoing:576:11)
at ServerResponse.header (C:\Users\LG\Desktop\gitProject\RunnerBe-Server\node_modules\express\lib\response.js:794:10)
at ServerResponse.send (C:\Users\LG\Desktop\gitProject\RunnerBe-Server\node_modules\express\lib\response.js:174:12)
at ServerResponse.json (C:\Users\LG\Desktop\gitProject\RunnerBe-Server\node_modules\express\lib\response.js:278:15)
at ServerResponse.send (C:\Users\LG\Desktop\gitProject\RunnerBe-Server\node_modules\express\lib\response.js:162:21)
at exports.getPosting2 (C:\Users\LG\Desktop\gitProject\RunnerBe-Server\src\app\Posting\postingController.js:467:11)
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
code: 'ERR_HTTP_HEADERS_SENT'
}
`
Expected behavior
A clear and concise description of what you expected to happen.
Screenshots
If applicable, add screenshots to help explain your problem.
Desktop (please complete the following information):
Smartphone (please complete the following information):
Additional context
Add any other context about the problem here.
쪽지와 참여신청 관련 13 ~~ 17번 API는 CMC 이후 리펙토링에서 사용할 수 있으니 남겨두고, 변경된 기획에 따라서 새로운 API 설계 필요
기획 확정된 이후 개발
Describe the bug
메인페이지에서 키워드 검색 조건이 제대로 전달이 안된다.
키워드 검색 관련 코드를 걷어내면 응답은 정상
컨트롤러 중
const keywordSearch = req.query.keywordSearch; // N : 필터 x, 그 외 키워드 검색
let keywordCondition = "";
if (keywordCondition != "N") {
keywordCondition += `AND INSTR(P.title, '${keywordSearch}') > 0 OR INSTR(P.contents, '${keywordSearch}') > 0`;
}
Dao 중
WHERE runningTag = "${runningTag}" ${distanceCondition}
${whetherEndCondition} ${genderCondition} ${jobCondition} ${ageCondition} ${keywordCondition}
cluster 에 참여하는 서버 정보와 포트를 upstream 지시자로 설정하며 첫 번째 설정한 서버가 우선적으로 응답을 처리함.
upstream node_proxy {
server localhost:3000;
server localhost:3001;
}
server {
(...)
location / {
proxy_pass http://node_proxy;
}
}
=> 가중치 없음, Round robin(RR) 형식의 load balancing 진행
https://www.lesstif.com/system-admin/nginx-load-balancing-35357063.html
https://github.com/winstonjs/winston
Usage
The recommended way to use winston is to create your own logger. The simplest way to do this is using winston.createLogger:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
defaultMeta: { service: 'user-service' },
transports: [
//
// - Write all logs with importance level of `error` or less to `error.log`
// - Write all logs with importance level of `info` or less to `combined.log`
//
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
//
// If we're not in production then log to the `console` with the format:
// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
//
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple(),
}));
}
최근에 504 error가 일어나 서버가 재시작된 것을 발견하였는데, logging을 꼼꼼히 처리하지 않아서 응답이 지연된 원인을 찾지 못했다.
우선 error 발생 가능한 구간은 모두 try catch 문을 통해 예외처리를 실시하여 서버가 터지지 않도록 한다.
설령 막지 못하더라도 pm2에 의해 재시작 되는데, 이때 디버깅을 위해서 log를 남겨야 한다.
따라서 winston을 사용하여 log를 남긴다.
예시)
try {
//start Transaction
connection.beginTransaction();
const patchPostingParams = [
title,
gatheringTime,
runningTime,
gatherLongitude,
gatherLatitude,
locationInfo,
runningTag,
ageMin,
ageMax,
peopleNum,
contents,
runnerGender,
postId,
];
//게시글 있는지 확인
const checkPostingResult = await postingProvider.checkPosting(postId);
if (checkPostingResult.length === 0)
return errResponse(baseResponse.POSTING_NOT_VALID_POSTID);
// 게시글 수정
const patchPostingResult = await postingDao.patchPosting(
connection,
patchPostingParams
);
//commit
await connection.commit();
return response(baseResponse.SUCCESS);
} catch (err) {
//rollback
await connection.rollback();
logger.error(`App - patchPosting Service error\n: ${err.message}`);
return errResponse(baseResponse.DB_ERROR);
} finally {
connection.release();
}
기존에는 try catch과 더불어 log를 남기는 코드가 부분적으로 적용되었는데, 전체 소스 코드를 검토하여 모두 반영하도록 작업을 진행한다.
3개월 확인 처리가 제대로 되지 않음
다른 validation이 작동하기 이전에 checkTerm에서 문제가 됨
-> 쿼리에서 User의 updatedAt을 기준으로 날짜를 계산했기 때문에 직군 변경이 아니라 다른 요소에도 영향을 받아 문제가 된 것
SELECT P.postId, P.createdAt as postingTime, postUserId, U.nickName, U.profileImageUrl, title,
case when date_format(gatheringTime, '%w') = 0
then date_format(gatheringTime,'%m/%d(일) %p%l:%i')
else
case when date_format(gatheringTime, '%w') = 1
then date_format(gatheringTime,'%m/%d(월) %p%l:%i')
else
case when date_format(gatheringTime, '%w') = 2
then date_format(gatheringTime,'%m/%d(화) %p%l:%i')
else
case when date_format(gatheringTime, '%w') = 3
then date_format(gatheringTime,'%m/%d(수) %p%l:%i')
else
case when date_format(gatheringTime, '%w') = 4
then date_format(gatheringTime,'%m/%d(목) %p%l:%i')
else
case when date_format(gatheringTime, '%w') = 5
then date_format(gatheringTime,'%m/%d(금) %p%l:%i')
else
case when date_format(gatheringTime, '%w') = 6
then date_format(gatheringTime,'%m/%d(토) %p%l:%i')
end end end end end end end as gatheringTime,
gatherLongitude, gatherLatitude, locationInfo, runningTag,concat(ageMin,'-',ageMax) as age,
case when runnerGender='A' then '전체'
else
case when runnerGender='M' then '남성'
else
case when runnerGender='F' then '여성'
end end end as gender,
left((6371 * acos(cos(radians(${userLatitude})) * cos(radians(gatherLatitude)) *
cos(radians(gatherLongitude) - radians(${userLongitude})) +
sin(radians(${userLatitude})) * sin(radians(gatherLatitude)))), 4) AS DISTANCE,
count(B.userId) as bookMarkNumber, whetherEnd
FROM Posting P
INNER JOIN User U on U.userId = P.postUserId
INNER JOIN Running R on R.postId = P.postId
LEFT OUTER JOIN Bookmarks B on P.postId = B.postId
WHERE runningTag = "${runningTag}"
${whetherEndCondition}
GROUP BY B.postId
ORDER BY "${sortCondition}";
#직군코드는 쿼리 따로 / 중복제거
SELECT DISTINCT postId, job
FROM RunningPeople RP
inner join Running R on RP.gatheringId = R.gatheringId
inner join User U on RP.userId = U.userId
WHERE whetherAccept = 'Y';
현재 게시글 당 여러 개의 직군 코드를 한 번에 담을 수 없음
서브쿼리를 사용하더라도 직군 코드를 제외한 나머지 컬럼들이 모두 중복되는 여러 개의 row가 생성됨
ex) DEV, DEV, EDU, PSV, DEV, DEV
=> [DEV, EDU, PSV] 하나의 컬럼으로 삽입
이렇게 담더라도 프론트에서 이를 분해할 수 있을 지 불명확함
=> 다른 방안을 찾아볼 것
현재는 게시글 객체와 별도로 직군코드 객체가 분리되니 프론트에서 postId로 맵핑하는 과정이 필요함
카카오
토큰 만료시키기
개발 시작하고 자료 보충
{
"isSuccess": true,
"code": 1000,
"message": "성공",
"result": {
"postingResult": [
{
"postId": 9,
"postingTime": "2022-02-10T18:25:58.000Z",
"postUserId": 2,
"nickName": "testsdf",
"profileImageUrl": null,
"title": "글제목1",
"gatheringTime": "05/22(일) AM11:22",
"gatherLongitude": "36.1231231230",
"gatherLatitude": "36.1231231230",
"locationInfo": "어디 어디",
"runningTag": "A",
"age": "20-65",
"gender": "남성",
"DISTANCE": "0",
"bookMarkNumber": 3,
"whetherEnd": "N"
},
{
"postId": 10,
"postingTime": "2021-02-11T06:41:59.000Z",
"postUserId": 20,
"nickName": "tsetzzz",
"profileImageUrl": null,
"title": "글제목2",
"gatheringTime": "05/17(화) AM11:22",
"gatherLongitude": "36.1231230000",
"gatherLatitude": "36.1232312300",
"locationInfo": "어디 어디 2",
"runningTag": "A",
"age": "25-45",
"gender": "전체",
"DISTANCE": "0.01",
"bookMarkNumber": 2,
"whetherEnd": "Y"
},
{
"postId": 11,
"postingTime": "2021-02-01T06:43:24.000Z",
"postUserId": 21,
"nickName": "test nick",
"profileImageUrl": null,
"title": "글제목3",
"gatheringTime": "05/21(토) AM11:22",
"gatherLongitude": "36.2312312300",
"gatherLatitude": "36.1312312300",
"locationInfo": "어디 어디 3",
"runningTag": "A",
"age": "30-40",
"gender": "여성",
"DISTANCE": "9.75",
"bookMarkNumber": 0,
"whetherEnd": "N"
}
],
"jobResult": [
{
"postId": 9,
"job": "교육"
},
{
"postId": 9,
"job": "개발"
},
{
"postId": 10,
"job": "개발"
},
{
"postId": 11,
"job": "공무원"
}
]
}
}
기존 쿼리에서 데이터를 묶는 방법을 찾지 못해서 job에 대한 데이터를 분리시킴
필수 데이터만을 골라 예시를 만들면,
SELECT DISTINCT postId, GROUP_CONCAT(distinct(job)) as job
FROM RunningPeople RP
inner join Running R on RP.gatheringId = R.gatheringId
inner join User U on RP.userId = U.userId
group by postId;
프로젝트에 적용시킬 것
config file을 통해 cluster mode 설정
https://engineering.linecorp.com/ko/blog/pm2-nodejs
let server = express().listen(port, () => {
process.send("ready"); //ready 전달
});
process.on("SIGINT", async () => {
await server.close();
process.exit(0);
});
module.exports = {
apps: [
{
name: "runnerbeApp",
script: "./index.js",
instances: 0,
exec_mode: "cluster",
wait_ready: true, // load하기 전에 ready
listen_timeout: 50000,
kill_timeout: 5000, // SIGINT -> SIGKILL 전송까지의 시간
},
],
};
pm2 start ./ecosystem.config.js --watch --ignore-watch="./log/*" -i max
현재 crypto 라이브러리로 email과 같은 회원의 개인 정보를 암호화 시켜서 DB에 저장하고 있다.
const hashedEmail = await crypto
.createHash("sha512")
.update(officeEmail)
단순 Hash를 통해 암호화 하기 때문에 rainbow table 공격이나 무차별 대입 공격에 취약하다.
따라서 Salt값이나 다중 Hash함수를 통해 보안성을 강화해야한다.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.