ํด๋น ํ๋ก์ ํธ GitHub Repository
https://github.com/HansungDomitory/HansungDomitory_Backend
GitHub - HansungDomitory/HansungDomitory_Backend
Contribute to HansungDomitory/HansungDomitory_Backend development by creating an account on GitHub.
github.com
1. ์ฃผ์ ์ค๋ช
ํ์ฑ๋ํ๊ต ์ฌ๋ฆ๋ฐฉํ ์ง๋กํ์ํ์ ์ ํ๋ก์ ํธ์ ๋๋ค.
ํ๊ต์์ ๋ฐฉํ๋์ ํ๋ ํน๋ณํ ํ๋์ธ ๋งํผ ์ด ๊ธฐ๊ฐ ๋์ ๊ผญ ๋ง๋ค์ด๋ณด๊ณ ์ถ์์ผ๋ฉด์๋ ํ๊ต์๋ ๋์์ด ๋ ๊ฒ ๊ฐ์ ์ฃผ์ ๋ก ํ๊ณ ์ถ๋ค๋ ์๊ฐ์ด ๋ค์์ต๋๋ค.
๊ทธ๋ฌ๋ค๊ฐ ํ๊ต ๊ธฐ์์ฌ ์ดํ๋ฆฌ์ผ์ด์ ์ ์ฌ์ฉํ๋๋ฐ ๋ถํธํ ์ ์ด ๊ฝค ๋ง์๋ ๊ธฐ์ต์ด ์์ด ์ด๋ฅผ ๋ชจํฐ๋ธํ๋ฉด ์ข์ ๊ฒ ๊ฐ์ ์ฃผ์ ๋ฅผ ํ์ฑ๋ ๊ธฐ์์ฌ ์ดํ๋ก ์ ์ ํ๊ฒ ๋์์ต๋๋ค.
2. ๊ฐ๋ฐ ๊ธฐ๊ฐ
2024.07.04 ~ 2024.08.05 (์ฝ 1๊ฐ์)
3. ์ญํ
๋ฐฑ์๋, ๋ฐฐํฌ ์๋ฒ ๊ด๋ฆฌ
- ์ค๊ณ
- ERD ์ค๊ณ
- ๋ก๊ทธ์ธ ๊ณผ์ ์ค๊ณ
- ๊ตฌํ
- ๋ก๊ทธ์ธ ๊ธฐ๋ฅ ๊ตฌํ
- ์ธ๋ฐ์ ์ฒญ ๊ธฐ๋ฅ ๊ตฌํ
- ์๋ฒ์ ๊ธฐ๋ฅ ๊ตฌํ
- ๊ณต์ง์ฌํญ ๊ธฐ๋ฅ ๊ตฌํ
- ๋ฐฐํฌ
- AWS EC2 ์ธ์คํด์ค ๋ฐฐํฌ
4. ๊ธฐ์ ์คํ
- ์ธ์ด: TypeScript
- ํ๋ ์์ํฌ: NestJS
- ๋ฐ์ดํฐ๋ฒ ์ด์ค: MySQL
- ๋ฐฐํฌ: AWS EC2
- ๋๊ตฌ: Docker, RestAPI, Swagger
5. ERD
6. ์ ์ฒด ๊ตฌ์กฐ
7. ์์ธ ๊ตฌํ ๋ด์ฉ
NestJS ํ๊ฒฝ์์ RestAPI๋ก ๋ฐฑ์๋ API๋ฅผ ๊ตฌํํ์ต๋๋ค.
7-1. ํ์ ๊ด๋ฆฌ
- JWT ํ ํฐ์ ํ์ฉํ ๋ก๊ทธ์ธ ๊ด๋ฆฌ
JWT ํ ํฐ์ ๋ง๋ค์ด์ ํ์์ ๋ก๊ทธ์ธ์ ๊ด๋ฆฌํ์ต๋๋ค.
JWT ํ ํฐ์ ๋ก๊ทธ์ธ ์ฒ๋ฆฌ๋ฅผ ๋ด๋นํ๋ accessToken๊ณผ accessToken ๊ธฐ๊ฐ ๋ง๋ฃ ์ ์ฌ๋ฐ๊ธ์ ๋ด๋นํ๋ refreshToken์ผ๋ก ๋ง๋ค์์ต๋๋ค.
refreshToken์ Cookie๋ฅผ ํตํด ํด๋ผ์ด์ธํธ์ ์ ๋ฌํ๋ ๋ฐฉ์์ ์ฌ์ฉํ์์ต๋๋ค.
[accessToken ๋ฐ๊ธ]
getAccessToken(student: Student | IStudentContext['student']): string {
return this.jwtService.sign(
{ sub: student.id },
{ secret: process.env.JWT_ACCESS_SECRET, expiresIn: '100m' }
);
}
[refreshToken ๋ฐ๊ธ]
getRefreshToken(student: Student, context: IContext): void {
const refreshToken = this.jwtService.sign(
{ sub: student.id },
{ secret: process.env.JWT_REFRESH_SECRET, expiresIn: '2w' },
);
context.res.setHeader(
'set-cookie',
`refreshToken=${refreshToken}; path=/;`,
);
}
- ์์ ์ ์ธ ๋น๋ฐ๋ฒํธ ๊ด๋ฆฌ
ํ์์ด ํ์๊ฐ์ ์ ํ ๋ ํ๋ฒ๊ณผ ๋น๋ฐ๋ฒํธ๋ฅผ ์ ๋ ฅํ๊ธฐ ๋๋ฌธ์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์ ์ ์ผ๋ก ์ ์ฅํ๋ ๊ณผ์ ์ด ํ์ํ์ต๋๋ค.
bcrypt์ hash ๊ธฐ๋ฅ์ ์ฌ์ฉํด์ ๋น๋ฐ๋ฒํธ๋ฅผ hash ์ฒ๋ฆฌํ ํ ์ ์ฅํ์๊ณ , ๋น๋ฐ๋ฒํธ ํ์ธ ์์๋ hash ๋ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ณตํธํํ์ฌ ๋งค์นญ์์ผฐ์ต๋๋ค.
[ํ์๊ฐ์ ์ ๋น๋ฐ๋ฒํธ hash ์ฒ๋ฆฌ]
async create({ createStudentInput }: IStudentServiceCreate): Promise<Student> {
...
const { password, ...rest} = createStudentInput;
const hashedPassword = await bcrypt.hash(password, 10);
...
}
7-2. ๊ด๋ฆฌ์ ์ค์
์๋ฒ์ ๊ด๋ฆฌ์ ๊ณต์ง์ฌํญ ์์ฑ์ ๊ด๋ฆฌ์๋ง ์ด์ฉํ ์ ์์ด์ผ ํ๊ธฐ ๋๋ฌธ์ ํ์๊ฐ์ ์ ์กด์ฌํ ์ ์๋ ํ๋ฒ์ธ '000000'์ ๊ฐ์ง ์ฌ์ฉ์๋ฅผ ๊ด๋ฆฌ์๋ก ๋ฑ๋กํ์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ , ์ ์์ ์ธ ๊ฒฝ๋ก๋ก ๋ง๋ค ์ ์๋ ๊ด๋ฆฌ์ ๊ณ์ ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ง์ ํ๋ฒ์ ์์ ํ์ฌ ๋ง๋ค์์ต๋๋ค.
[ํ์๊ฐ์ ์ ๊ด๋ฆฌ์ ๊ณ์ ์์ฑ ๋ฐฉ์ง]
async create({ createStudentInput }: IStudentServiceCreate): Promise<Student> {
...
if(createStudentInput.id === '0000000') {
throw new BadRequestException('ํ์๊ฐ์
ํ ์ ์๋ ID์
๋๋ค.');
}
…
}
๊ด๋ฆฌ์๋ง ํธ์ถํ ์ ์๋ API์ ํธ์ถํ ์ฌ์ฉ์๊ฐ ๊ด๋ฆฌ์์ธ์ง ํ์ธํ๋ ์์ ์ ์ถ๊ฐํ๊ณ , ๊ด๋ฆฌ์๊ฐ ์๋ ์์ ForbiddenException ์์ธ๋ฅผ ๋ฐ์์์ผ์ ์ผ๋ฐ ์ฌ์ฉ์๊ฐ ์ฌ์ฉํ ์ ์๋๋ก ๊ตฌํํ์ต๋๋ค.
[์๋ฒ์ ์ถ๊ฐ API์์ ๊ด๋ฆฌ์ ํ์ธ ์ ์ฐจ ์ถ๊ฐ]
async create(myId: string, createScoreRecordInput: CreateScoreRecordInput) {
if(!myId || myId !== '0000000') {
throw new ForbiddenException('๊ด๋ฆฌ์๋ง ์๋ฒ์ ๊ธฐ๋ก์ ์์ฑํ ์ ์์ต๋๋ค.');
}
…
}
7-3. ๋๋ค ๋ฐฉ ๋ฐฐ์
๊ธฐ์์ฌ ์ดํ๋ฆฌ์ผ์ด์ ์ ๊ฐ์ ํ ํ์๋ค์ ๊ฐ์ ๋น ๋ฐฉ ๋ฒํธ๊ฐ ๋ฐฐ์ ์ด ๋์ด์ผ ํฉ๋๋ค.
๋ฐ๋ผ์, ๋ฐฐ์ ์ด ๋์ง ์์ ๋น ๋ฐฉ์ ์ฐพ๋ ๊ณผ์ ๊ณผ ๋ฐฐ์ ๋์ง ์์ ๋น ๋ฐฉ์ ํ์ ๊ฐ์ ํ ํ์์๊ฒ ๋ฐฐ์ ํ๋ ๊ณผ์ ์ ๊ตฌํํ์ต๋๋ค.
[๋๋ค ๋ฐฉ ๋ฒํธ ์์ฑ]
private generateRandomRoom(): string {
const floor = this.getRandomInt(1, 9);
const roomSuffix = this.getRandomInt(1, 9);
const tailDasi = this.getRandomInt(1, 2);
return `${floor}0${roomSuffix}-${tailDasi}`;
}
[์ฌ์ฉ๋์ง ์์ ๋๋ค ๋ฐฉ ๋ฒํธ ๋ฐฐ์ ]
private async assignRandomRoom(): Promise<string> {
const existingRooms = await this.studentRepository.find({ select: ['room'] });
const existingRoomSet = new Set(existingRooms.map(student => student.room));
while (true) {
const randomRoom = this.generateRandomRoom();
if (!existingRoomSet.has(randomRoom)) {
return randomRoom;
}
}
}
7-4. ์๋ฒ์ ๊ด๋ฆฌ
์๋ฒ์ ์ ๊ด๋ฆฌ์๊ฐ ํ์์๊ฒ ๋ถ์ฌํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ์ต๋๋ค.
์์ ๊ณผ ๋ฒ์ ์ ๋ฐ๋ก ๊ตฌํํ์ง ์๊ณ , ํ๋์ ์ ์ ํ๋๋ก ์์ฑํ ํ ์์๋ ์์ ์ผ๋ก, ์์๋ ๋ฒ์ ์ผ๋ก ๊ตฌํํ์ต๋๋ค.
ํ๋์ ํ๋๋ก๋ง ๊ตฌํํ ์ด์
์์ ์ด ์๋ ์ํ์์ ๋ฒ์ ์ด ๋ถ์ฌ๋๋ฉด ๊ทธ๋งํผ ์์ ์ด ์ฐจ๊ฐ๋๋ ๊ฒ์ฒ๋ผ ์์ ๊ณผ ๋ฒ์ ์ ์๋ก ์ฐ๊ด๋์ด ์๋ ์ํ์ด๊ธฐ ๋๋ฌธ์, ์์ ๊ณผ ๋ฒ์ ์ ๋ฐ๋ก ๋๋ ์ ๊ตฌํํ์ง ์๊ณ ๊ฐ๋จํ๊ฒ ์ ์๋ผ๋ ํ๋์ ํ๋๋ง ๊ฐ๊ฒ ํ์ต๋๋ค.
[์ ์ ํ๋ ํ๋๋ก๋ง ๊ตฌํํ ์๋ฒ์ ๊ธฐ๋ฅ ์ํฐํฐ]
@Entity()
export class ScoreRecord {
@ApiProperty({ description: "๊ณ ์ ๋ฒํธ" })
@PrimaryGeneratedColumn('increment')
id: number;
@ApiProperty({ description: "ํด๋น ๊ธฐ๋ก ๊ด๋ จ ํ์" })
@ManyToOne(() => Student, { onDelete: 'CASCADE' })
@JoinColumn()
student: Student;
@ApiProperty({ description: "์์ /๋ฒ์ ์ฌ๋ถ" })
@Column()
is_merit: boolean;
@ApiProperty({ description: "์ ์" })
@Column()
score: number;
@ApiProperty({ description: "์์ธ๋ด์ฉ" })
@Column()
detail: string;
@ApiProperty({ description: "์์ฑ์ผ์" })
@CreateDateColumn()
create_at: Date;
}
8. ์คํ ์๋๋ฆฌ์ค
8-1. ํ์๊ฐ์ , ๋ก๊ทธ์ธ
- ํ์๊ฐ์
: ์ด๋ฆ, ํ๋ฒ, ๋น๋ฐ๋ฒํธ๋ฅผ ๋ค ์
๋ ฅํด์ผ ํ์๊ฐ์
์ด ๊ฐ๋ฅํฉ๋๋ค.
- ํ์๊ฐ์ ์ ํ๋ฉด ๋๋ค์ผ๋ก ๊ธฐ์์ฌ ๋น ๋ฐฉ์ด ๋ฐฐ์ ๋ฉ๋๋ค.
- ๋ก๊ทธ์ธ: ํ์๊ฐ์ ํ, ํ๋ฒ๊ณผ ๋น๋ฐ๋ฒํธ๋ฅผ ์ ๋ ฅํ๋ฉด ๋ก๊ทธ์ธ์ด ๋ฉ๋๋ค.
8-2. ํ ํ๋ฉด ๋ฐ ๊ธฐ์์ฌ ์๊ฐ ํ๋ฉด
- ํ ํ๋ฉด: ๋ฉ์ธ ํ๋ฉด์ผ๋ก, ํด๋น ํ์ด์ง์์ ๊ธฐ์์ฌ ์๊ฐ ํ์ด์ง์ ๊ณต์ง์ฌํญ ํ์ด์ง๋ก ์ด๋ํ ์ ์์ต๋๋ค.
- ๊ธฐ์์ฌ ์๊ฐ ํ๋ฉด: ํ์ฑ๋ํ๊ต ๊ธฐ์์ฌ์ ๊ดํ ์ ๋ณด๊ฐ ๋์์์ต๋๋ค.
8-3. ์ธ๋ฐ์ ์ฒญ
- ์ด ํ์ด์ง์์ ์ธ๋ฐ ์ ์ฒญ์ด ๊ฐ๋ฅํ๋ฉฐ, ํ๋ฒ๊ณผ ์ด๋ฆ์ ์๋์ผ๋ก ๋ถ๋ฌ์์ง๊ณ ๋ ์ง๋ฅผ ์ ํํ๋ฉด ์๋์ผ๋ก ์ธ๋ฐ์ผ์๊ฐ ๋ณด์ฌ์ง๋๋ค.
- ๋ ์ง๋ฅผ ์ ํํ๊ณ ๋ด์ฉ๊น์ง ์ ์ด์ ์ ์ฒญํ๊ธฐ ๋ฒํผ์ ๋๋ฅด๋ฉด ์ธ๋ฐ ํํฉ ํ์ด์ง๊ฐ ๋์ค๊ณ , ๊ทธ ํ์ด์ง์์ ๊ธฐ๊ฐ๊ณผ ์ธ๋ฐ์ผ์, ๋ฑ๋ก์ผ์๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
- ์ธ๋ฐ ์ ์ฒญํ๋ ๋ชฉ๋ก๋ค์ ๋ฑ๋ก์ผ์ ๊ธฐ์ค์ผ๋ก ๋ด๋ฆผ์ฐจ์์ผ๋ก ๋ฐฐ์ด๋์ด ์์ผ๋ฉฐ, ์ธ๋ฐ ๊ธฐ๊ฐ์ด ๋๊ธฐ ์ ์๋ ์์ ๊ณผ ์ญ์ ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
8-4. ๋ง์ดํ์ด์ง, ์๋ฒ์ ์กฐํ
- ๋ง์ดํ์ด์ง
- ํ์ ์ด๋ฆ, ๋ฐฐ์ ๋ ๋ฐฉ์ด ๋์ ์์ผ๋ฉฐ, ํด๋น ํ์ด์ง์์ ๋ก๊ทธ์์ ๋ฒํผ์ ๋๋ฅด๋ฉด ๋ก๊ทธ์์์ด ๊ฐ๋ฅํฉ๋๋ค.
- ํด๋น ํ์ด์ง์์ ๊ณต์ง์ฌํญ ํ์ด์ง, ์ธ๋ฐํํฉ ํ์ด์ง, ์๋ฒ์ ํํฉ ํ์ด์ง๋ก ์ด๋ํ ์ ์์ต๋๋ค.
- ์๋ฒ์ ์กฐํ
- ํ์ ์ ๋ณด์ ์๋ฒ์ ์ ์, ์ด์ , ์๋ฒ์ ์ ๊ดํ ์ฌ์ ์ ์ ์ฉ์ผ์๊ฐ ๋์์๊ณ ๋ด๋ฆผ์ฐจ์์ผ๋ก ๋์ด๋์ด ์์ต๋๋ค.
- ํ๋์ +์ ์๋ ๊ธฐ์กด ์ ์์์ ๊ฐ์ฐ๋๊ณ , ๋นจ๊ฐ์ -์ ์๋ ๊ธฐ์กด ์ ์์์ ๊ฐ์ฐ๋ฉ๋๋ค.
8-5. ๊ณต์ง์ฌํญ
- ๊ด๋ฆฌ์์ธก์์ ๊ธฐ์์ฌ์ ๊ด๋ จ๋ ๊ณต์ง์ฌํญ์ ์ฌ๋ฆฌ๋ฉด ํ์๋ค์ ์ด ๊ณต์ง์ฌํญ๋ค์ ํ์ธํ ์ ์์ต๋๋ค.
- ๊ณต์ง์ฌํญ์ ๊ฐ์ฅ ์ค๋๋ ๊ธ๋ถํฐ ๋ด๋ฆผ์ฐจ์์ผ๋ก ์ฌ๋ ค์ ธ ์๊ณ , ํ์๋ค์ ๊ฒ์์ ํตํด ๊ถ๊ธํ ๊ณต์ง ์ฌํญ์ ๋ณผ ์ ์์ต๋๋ค.
'PROJECT๐ป' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Web] ์์ทจ๋ง๋ ํ๋ก์ ํธ (0) | 2025.02.11 |
---|---|
[Android] Green Market ํ๋ก์ ํธ (0) | 2025.02.10 |