diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 8469558..5a5cfa3 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,464 +1,315 @@ # ๐Ÿ—๏ธ Whale Town ้กน็›ฎๆžถๆž„่ฎพ่ฎก -> ๅŸบไบŽไธšๅŠกๅŠŸ่ƒฝๆจกๅ—ๅŒ–็š„็ŽฐไปฃๅŒ–ๅŽ็ซฏๆžถๆž„๏ผŒๆ”ฏๆŒๅŒๆจกๅผ่ฟ่กŒ๏ผŒๅผ€ๅ‘ๆต‹่ฏ•้›ถไพ่ต–๏ผŒ็”Ÿไบง้ƒจ็ฝฒ้ซ˜ๆ€ง่ƒฝใ€‚ +> ๅŸบไบŽๅ››ๅฑ‚ๆžถๆž„๏ผˆGateway-Business-Core-Data๏ผ‰็š„็ŽฐไปฃๅŒ–ๅŽ็ซฏ่ฎพ่ฎก๏ผŒๆ”ฏๆŒๅŒๆจกๅผ่ฟ่กŒ๏ผŒๅผ€ๅ‘ๆต‹่ฏ•้›ถไพ่ต–๏ผŒ็”Ÿไบง้ƒจ็ฝฒ้ซ˜ๆ€ง่ƒฝใ€‚ ## ๐Ÿ“‹ ็›ฎๅฝ• -- [๐ŸŽฏ ๆžถๆž„ๆฆ‚่ฟฐ](#-ๆžถๆž„ๆฆ‚่ฟฐ) -- [๐Ÿ“ ็›ฎๅฝ•็ป“ๆž„่ฏฆ่งฃ](#-็›ฎๅฝ•็ป“ๆž„่ฏฆ่งฃ) -- [๐Ÿ—๏ธ ๅˆ†ๅฑ‚ๆžถๆž„่ฎพ่ฎก](#๏ธ-ๅˆ†ๅฑ‚ๆžถๆž„่ฎพ่ฎก) -- [๐Ÿ”„ ๅŒๆจกๅผๆžถๆž„](#-ๅŒๆจกๅผๆžถๆž„) -- [๐Ÿ“ฆ ๆจกๅ—ไพ่ต–ๅ…ณ็ณป](#-ๆจกๅ—ไพ่ต–ๅ…ณ็ณป) -- [๐Ÿš€ ๆ‰ฉๅฑ•ๆŒ‡ๅ—](#-ๆ‰ฉๅฑ•ๆŒ‡ๅ—) +- [ๆžถๆž„ๆฆ‚่ฟฐ](#ๆžถๆž„ๆฆ‚่ฟฐ) +- [ๅ››ๅฑ‚ๆžถๆž„่ฎพ่ฎก](#ๅ››ๅฑ‚ๆžถๆž„่ฎพ่ฎก) +- [็›ฎๅฝ•็ป“ๆž„](#็›ฎๅฝ•็ป“ๆž„) +- [ๅŒๆจกๅผๆžถๆž„](#ๅŒๆจกๅผๆžถๆž„) +- [ๆจกๅ—ไพ่ต–ๅ…ณ็ณป](#ๆจกๅ—ไพ่ต–ๅ…ณ็ณป) +- [ๆ•ฐๆฎๆตๅ‘](#ๆ•ฐๆฎๆตๅ‘) +- [ๆ‰ฉๅฑ•ๆŒ‡ๅ—](#ๆ‰ฉๅฑ•ๆŒ‡ๅ—) --- -## ๐ŸŽฏ ๆžถๆž„ๆฆ‚่ฟฐ +## ๆžถๆž„ๆฆ‚่ฟฐ -Whale Town ้‡‡็”จ**ไธšๅŠกๅŠŸ่ƒฝๆจกๅ—ๅŒ–ๆžถๆž„**๏ผŒๅฐ†ไปฃ็ ๆŒ‰ไธšๅŠกๅŠŸ่ƒฝ่€Œ้žๆŠ€ๆœฏ็ป„ไปถ็ป„็ป‡๏ผŒ็กฎไฟ้ซ˜ๅ†…่šใ€ไฝŽ่€ฆๅˆ็š„่ฎพ่ฎกๅŽŸๅˆ™ใ€‚ +Whale Town ้‡‡็”จ**ๅ››ๅฑ‚ๆžถๆž„่ฎพ่ฎก**๏ผˆGateway-Business-Core-Data๏ผ‰๏ผŒๅฐ†ๅ่ฎฎๅค„็†ใ€ไธšๅŠก้€ป่พ‘ใ€ๆŠ€ๆœฏๅŸบ็ก€่ฎพๆ–ฝๅ’Œๆ•ฐๆฎๅญ˜ๅ‚จๆธ…ๆ™ฐๅˆ†็ฆปใ€‚ -### ๐ŸŒŸ ๆ ธๅฟƒ่ฎพ่ฎก็†ๅฟต +### ๆ ธๅฟƒ่ฎพ่ฎก็†ๅฟต -- **ไธšๅŠก้ฉฑๅŠจ** - ๆŒ‰ไธšๅŠกๅŠŸ่ƒฝ็ป„็ป‡ไปฃ็ ๏ผŒ่€Œ้žๆŠ€ๆœฏๅˆ†ๅฑ‚ +- **่Œ่ดฃๅˆ†็ฆป** - ๆฏๅฑ‚่Œ่ดฃๆ˜Ž็กฎ๏ผŒไบ’ไธๅนฒๆ‰ฐ - **ๅŒๆจกๅผๆ”ฏๆŒ** - ๅผ€ๅ‘ๆต‹่ฏ•้›ถไพ่ต–๏ผŒ็”Ÿไบง้ƒจ็ฝฒ้ซ˜ๆ€ง่ƒฝ -- **ๆธ…ๆ™ฐๅˆ†ๅฑ‚** - ไธšๅŠกๅฑ‚ โ†’ ๆ ธๅฟƒๅฑ‚ โ†’ ๆ•ฐๆฎๅฑ‚๏ผŒ่Œ่ดฃๆ˜Ž็กฎ -- **ๆจกๅ—ๅŒ–่ฎพ่ฎก** - ๆฏไธชๆจกๅ—็‹ฌ็ซ‹ๅฎŒๆ•ด๏ผŒๅฏๅ•็‹ฌๆต‹่ฏ•ๅ’Œ้ƒจ็ฝฒ -- **้…็ฝฎ้ฉฑๅŠจ** - ้€š่ฟ‡็Žฏๅขƒๅ˜้‡ๆŽงๅˆถ่ฟ่กŒๆจกๅผๅ’Œ่กŒไธบ +- **ไพ่ต–ๅ•ๅ‘** - ไธŠๅฑ‚ไพ่ต–ไธ‹ๅฑ‚๏ผŒไธ‹ๅฑ‚ไธไพ่ต–ไธŠๅฑ‚ +- **ๆจกๅ—ๅŒ–่ฎพ่ฎก** - ๆฏไธชๆจกๅ—็‹ฌ็ซ‹ๅฎŒๆ•ด๏ผŒๅฏๅ•็‹ฌๆต‹่ฏ• +- **้…็ฝฎ้ฉฑๅŠจ** - ้€š่ฟ‡็Žฏๅขƒๅ˜้‡ๆŽงๅˆถ่ฟ่กŒๆจกๅผ -### ๐Ÿ› ๏ธ ๆŠ€ๆœฏๆ ˆ +### ๆŠ€ๆœฏๆ ˆ -#### ๅŽ็ซฏๆŠ€ๆœฏๆ ˆ -- **ๆก†ๆžถ**: NestJS 11.x (ๅŸบไบŽExpress) -- **่ฏญ่จ€**: TypeScript 5.x -- **ๆ•ฐๆฎๅบ“**: MySQL + TypeORM (็”Ÿไบง) / ๅ†…ๅญ˜ๆ•ฐๆฎๅบ“ (ๅผ€ๅ‘) -- **็ผ“ๅญ˜**: Redis + IORedis (็”Ÿไบง) / ๆ–‡ไปถๅญ˜ๅ‚จ (ๅผ€ๅ‘) -- **่ฎค่ฏ**: JWT + bcrypt -- **้ชŒ่ฏ**: class-validator + class-transformer -- **ๆ–‡ๆกฃ**: Swagger/OpenAPI -- **ๆต‹่ฏ•**: Jest + Supertest -- **ๆ—ฅๅฟ—**: Pino + nestjs-pino -- **WebSocket**: Socket.IO -- **้‚ฎไปถ**: Nodemailer -- **้›†ๆˆ**: Zulip API - -#### ๅ‰็ซฏๆŠ€ๆœฏๆ ˆ -- **ๆก†ๆžถ**: React 18.x -- **ๆž„ๅปบๅทฅๅ…ท**: Vite 7.x -- **UIๅบ“**: Ant Design 5.x -- **่ทฏ็”ฑ**: React Router DOM 6.x -- **่ฏญ่จ€**: TypeScript 5.x - -### ๐Ÿ“Š ๆ•ดไฝ“ๆžถๆž„ๅ›พ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ๐ŸŒ APIๆŽฅๅฃๅฑ‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ๐Ÿ”— REST API โ”‚ โ”‚ ๐Ÿ”Œ WebSocket โ”‚ โ”‚ ๐Ÿ“„ Swagger UI โ”‚ โ”‚ -โ”‚ โ”‚ (HTTPๆŽฅๅฃ) โ”‚ โ”‚ (ๅฎžๆ—ถ้€šไฟก) โ”‚ โ”‚ (APIๆ–‡ๆกฃ) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โฌ‡๏ธ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ๐ŸŽฏ ไธšๅŠกๅŠŸ่ƒฝๆจกๅ—ๅฑ‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ๐Ÿ” ็”จๆˆท่ฎค่ฏ โ”‚ โ”‚ ๐Ÿ‘ฅ ็”จๆˆท็ฎก็† โ”‚ โ”‚ ๐Ÿ›ก๏ธ ็ฎก็†ๅ‘˜ โ”‚ โ”‚ -โ”‚ โ”‚ (auth) โ”‚ โ”‚ (user_mgmt) โ”‚ โ”‚ (admin) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ๐Ÿ’ฌ Zulip้›†ๆˆ โ”‚ โ”‚ ๐Ÿ”— ๅ…ฑไบซ็ป„ไปถ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ (zulip) โ”‚ โ”‚ (shared) โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โฌ‡๏ธ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ โš™๏ธ ๆ ธๅฟƒๆŠ€ๆœฏๆœๅŠกๅฑ‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ๐Ÿ”‘ ็™ปๅฝ•ๆ ธๅฟƒ โ”‚ โ”‚ ๐Ÿ‘‘ ็ฎก็†ๅ‘˜ๆ ธๅฟƒ โ”‚ โ”‚ ๐Ÿ’ฌ Zulipๆ ธๅฟƒ โ”‚ โ”‚ -โ”‚ โ”‚ (auth_core) โ”‚ โ”‚ (admin_core) โ”‚ โ”‚ (zulip) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ๐Ÿ›ก๏ธ ๅฎ‰ๅ…จๆ ธๅฟƒ โ”‚ โ”‚ ๐Ÿ› ๏ธ ๅทฅๅ…ทๆœๅŠก โ”‚ โ”‚ ๐Ÿ“ง ้‚ฎไปถๆœๅŠก โ”‚ โ”‚ -โ”‚ โ”‚ (security_core)โ”‚ โ”‚ (utils) โ”‚ โ”‚ (email) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โฌ‡๏ธ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ๐Ÿ—„๏ธ ๆ•ฐๆฎๅญ˜ๅ‚จๅฑ‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ๐Ÿ—ƒ๏ธ ๆ•ฐๆฎๅบ“ โ”‚ โ”‚ ๐Ÿ”ด Redis็ผ“ๅญ˜ โ”‚ โ”‚ ๐Ÿ“ ๆ–‡ไปถๅญ˜ๅ‚จ โ”‚ โ”‚ -โ”‚ โ”‚ (MySQL/ๅ†…ๅญ˜) โ”‚ โ”‚ (Redis/ๆ–‡ไปถ) โ”‚ โ”‚ (logs/data) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` +**ๅŽ็ซฏ๏ผš** NestJS 11 + TypeScript 5 + MySQL + Redis + ๅŽŸ็”ŸWebSocket +**ๅ‰็ซฏ๏ผš** React 18 + Vite 7 + Ant Design 5 +**ๆต‹่ฏ•๏ผš** Jest + Supertest๏ผˆ99ไธชๆต‹่ฏ•็”จไพ‹๏ผ‰ +**้ƒจ็ฝฒ๏ผš** Docker + PM2 + Nginx --- -## ๐Ÿ“ ็›ฎๅฝ•็ป“ๆž„่ฏฆ่งฃ +## ๅ››ๅฑ‚ๆžถๆž„่ฎพ่ฎก -### ๐ŸŽฏ ไธšๅŠกๅŠŸ่ƒฝๆจกๅ— (`src/business/`) +### ๆžถๆž„ๅฑ‚ๆฌกๅ›พ -> **่ฎพ่ฎกๅŽŸๅˆ™**: ๆŒ‰ไธšๅŠกๅŠŸ่ƒฝ็ป„็ป‡๏ผŒๆฏไธชๆจกๅ—ๅŒ…ๅซๅฎŒๆ•ด็š„ไธšๅŠก้€ป่พ‘ +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐ŸŒ Gateway Layer (็ฝ‘ๅ…ณๅฑ‚) โ”‚ +โ”‚ HTTP/WebSocketๅ่ฎฎๅค„็†ใ€ๆ•ฐๆฎ้ชŒ่ฏใ€่ทฏ็”ฑ็ฎก็†ใ€่ฎค่ฏๅฎˆๅซ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โฌ‡๏ธ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐ŸŽฏ Business Layer (ไธšๅŠกๅฑ‚) โ”‚ +โ”‚ ไธšๅŠก้€ป่พ‘ๅฎž็Žฐใ€ๆœๅŠกๅ่ฐƒใ€ไธšๅŠก่ง„ๅˆ™้ชŒ่ฏใ€ไบ‹ๅŠก็ฎก็† โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โฌ‡๏ธ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โš™๏ธ Core Layer (ๆ ธๅฟƒๅฑ‚) โ”‚ +โ”‚ ๆŠ€ๆœฏๅŸบ็ก€่ฎพๆ–ฝใ€ๆ•ฐๆฎ่ฎฟ้—ฎใ€ๅค–้ƒจ็ณป็ปŸ้›†ๆˆใ€ๅทฅๅ…ทๆœๅŠก โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โฌ‡๏ธ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ—„๏ธ Data Layer (ๆ•ฐๆฎๅฑ‚) โ”‚ +โ”‚ ๆ•ฐๆฎๆŒไน…ๅŒ–ใ€็ผ“ๅญ˜็ฎก็†ใ€ๆ–‡ไปถๅญ˜ๅ‚จ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### ๅ„ๅฑ‚่Œ่ดฃ + +#### ๐ŸŒ Gateway Layer๏ผˆ็ฝ‘ๅ…ณๅฑ‚๏ผ‰ + +**ไฝ็ฝฎ๏ผš** `src/gateway/` + +**่Œ่ดฃ๏ผš** +- HTTP/WebSocketๅ่ฎฎๅค„็† +- ่ฏทๆฑ‚ๅ‚ๆ•ฐ้ชŒ่ฏ๏ผˆDTO๏ผ‰ +- ่ทฏ็”ฑ็ฎก็† +- ่ฎค่ฏๅฎˆๅซ๏ผˆJWT้ชŒ่ฏ๏ผ‰ +- ้”™่ฏฏ่ฝฌๆข๏ผˆไธšๅŠก้”™่ฏฏ โ†’ HTTP็Šถๆ€็ ๏ผ‰ +- APIๆ–‡ๆกฃ๏ผˆSwagger๏ผ‰ + +**ๅŽŸๅˆ™๏ผš** +- โœ… ๅชๅšๅ่ฎฎ่ฝฌๆข๏ผŒไธๅšไธšๅŠก้€ป่พ‘ +- โœ… ไฝฟ็”จDTO่ฟ›่กŒๆ•ฐๆฎ้ชŒ่ฏ +- โœ… ็ปŸไธ€็š„้”™่ฏฏๅค„็† +- โŒ ไธ็›ดๆŽฅ่ฎฟ้—ฎๆ•ฐๆฎๅบ“ +- โŒ ไธๅŒ…ๅซไธšๅŠก่ง„ๅˆ™ + +**็คบไพ‹ๆจกๅ—๏ผš** +- `gateway/auth/` - ่ฎค่ฏ็ฝ‘ๅ…ณ๏ผˆ็™ปๅฝ•ใ€ๆณจๅ†ŒๆŽฅๅฃ๏ผ‰ +- `gateway/location_broadcast/` - ไฝ็ฝฎๅนฟๆ’ญ็ฝ‘ๅ…ณ๏ผˆWebSocket๏ผ‰ + +#### ๐ŸŽฏ Business Layer๏ผˆไธšๅŠกๅฑ‚๏ผ‰ + +**ไฝ็ฝฎ๏ผš** `src/business/` + +**่Œ่ดฃ๏ผš** +- ไธšๅŠก้€ป่พ‘ๅฎž็Žฐ +- ไธšๅŠกๆต็จ‹ๆŽงๅˆถ +- ๆœๅŠกๅ่ฐƒ +- ไธšๅŠก่ง„ๅˆ™้ชŒ่ฏ +- ไบ‹ๅŠก็ฎก็† + +**ๅŽŸๅˆ™๏ผš** +- โœ… ๅฎž็Žฐๆ‰€ๆœ‰ไธšๅŠก้€ป่พ‘ +- โœ… ๅ่ฐƒๅคšไธชCoreๅฑ‚ๆœๅŠก +- โœ… ่ฟ”ๅ›ž็ปŸไธ€็š„ไธšๅŠกๅ“ๅบ” +- โŒ ไธๅค„็†HTTPๅ่ฎฎ +- โŒ ไธ็›ดๆŽฅ่ฎฟ้—ฎๆ•ฐๆฎๅบ“ + +**็คบไพ‹ๆจกๅ—๏ผš** +- `business/auth/` - ็”จๆˆท่ฎค่ฏไธšๅŠก +- `business/user_mgmt/` - ็”จๆˆท็ฎก็†ไธšๅŠก +- `business/admin/` - ็ฎก็†ๅ‘˜ไธšๅŠก +- `business/zulip/` - Zulip้›†ๆˆไธšๅŠก +- `business/location_broadcast/` - ไฝ็ฝฎๅนฟๆ’ญไธšๅŠก +- `business/notice/` - ๅ…ฌๅ‘ŠไธšๅŠก + +#### โš™๏ธ Core Layer๏ผˆๆ ธๅฟƒๅฑ‚๏ผ‰ + +**ไฝ็ฝฎ๏ผš** `src/core/` + +**่Œ่ดฃ๏ผš** +- ๆ•ฐๆฎ่ฎฟ้—ฎ๏ผˆๆ•ฐๆฎๅบ“ใ€็ผ“ๅญ˜๏ผ‰ +- ๅŸบ็ก€่ฎพๆ–ฝ๏ผˆRedisใ€ๆถˆๆฏ้˜Ÿๅˆ—๏ผ‰ +- ๅค–้ƒจ็ณป็ปŸ้›†ๆˆ๏ผˆZulip API๏ผ‰ +- ๆŠ€ๆœฏๅฎž็Žฐ็ป†่Š‚ +- ๅทฅๅ…ทๆœๅŠก๏ผˆ้‚ฎไปถใ€้ชŒ่ฏ็ ใ€ๆ—ฅๅฟ—๏ผ‰ + +**ๅŽŸๅˆ™๏ผš** +- โœ… ๆไพ›ๆŠ€ๆœฏๅŸบ็ก€่ฎพๆ–ฝ +- โœ… ๆ•ฐๆฎๆŒไน…ๅŒ–ๅ’Œ็ผ“ๅญ˜ +- โœ… ๅค–้ƒจAPI้›†ๆˆ +- โŒ ไธๅŒ…ๅซไธšๅŠก้€ป่พ‘ +- โŒ ไธๅค„็†HTTPๅ่ฎฎ + +**็คบไพ‹ๆจกๅ—๏ผš** +- `core/db/users/` - ็”จๆˆทๆ•ฐๆฎๆœๅŠก +- `core/redis/` - Redis็ผ“ๅญ˜ๆœๅŠก +- `core/login_core/` - ็™ปๅฝ•ๆ ธๅฟƒๆœๅŠก +- `core/admin_core/` - ็ฎก็†ๅ‘˜ๆ ธๅฟƒๆœๅŠก +- `core/zulip_core/` - Zulipๆ ธๅฟƒๆœๅŠก +- `core/security_core/` - ๅฎ‰ๅ…จๆ ธๅฟƒๆœๅŠก +- `core/utils/` - ๅทฅๅ…ทๆœๅŠก๏ผˆ้‚ฎไปถใ€้ชŒ่ฏ็ ใ€ๆ—ฅๅฟ—๏ผ‰ + +#### ๏ฟฝ๏ธ Data Layer๏ผˆๆ•ฐๆฎๅฑ‚๏ผ‰ + +**ไฝ็ฝฎ๏ผš** ๆ•ฐๆฎๅบ“ใ€Redisใ€ๆ–‡ไปถ็ณป็ปŸ + +**่Œ่ดฃ๏ผš** +- ๆ•ฐๆฎๆŒไน…ๅŒ– +- ็ผ“ๅญ˜็ฎก็† +- ๆ–‡ไปถๅญ˜ๅ‚จ + +**ๅฎž็Žฐ๏ผš** +- MySQL / ๅ†…ๅญ˜ๆ•ฐๆฎๅบ“ +- Redis / ๆ–‡ไปถๅญ˜ๅ‚จ +- ๆ—ฅๅฟ—ๆ–‡ไปถ / ๆ•ฐๆฎๆ–‡ไปถ + +--- + +## ็›ฎๅฝ•็ป“ๆž„ + +### ๆ•ดไฝ“็ป“ๆž„ + +``` +whale-town-end/ +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ gateway/ # ๐ŸŒ ็ฝ‘ๅ…ณๅฑ‚ +โ”‚ โ”‚ โ”œโ”€โ”€ auth/ # ่ฎค่ฏ็ฝ‘ๅ…ณ +โ”‚ โ”‚ โ””โ”€โ”€ location_broadcast/ # ไฝ็ฝฎๅนฟๆ’ญ็ฝ‘ๅ…ณ +โ”‚ โ”œโ”€โ”€ business/ # ๐ŸŽฏ ไธšๅŠกๅฑ‚ +โ”‚ โ”‚ โ”œโ”€โ”€ auth/ # ็”จๆˆท่ฎค่ฏไธšๅŠก +โ”‚ โ”‚ โ”œโ”€โ”€ user_mgmt/ # ็”จๆˆท็ฎก็†ไธšๅŠก +โ”‚ โ”‚ โ”œโ”€โ”€ admin/ # ็ฎก็†ๅ‘˜ไธšๅŠก +โ”‚ โ”‚ โ”œโ”€โ”€ zulip/ # Zulip้›†ๆˆไธšๅŠก +โ”‚ โ”‚ โ”œโ”€โ”€ location_broadcast/ # ไฝ็ฝฎๅนฟๆ’ญไธšๅŠก +โ”‚ โ”‚ โ””โ”€โ”€ notice/ # ๅ…ฌๅ‘ŠไธšๅŠก +โ”‚ โ”œโ”€โ”€ core/ # โš™๏ธ ๆ ธๅฟƒๅฑ‚ +โ”‚ โ”‚ โ”œโ”€โ”€ db/users/ # ็”จๆˆทๆ•ฐๆฎๆœๅŠก +โ”‚ โ”‚ โ”œโ”€โ”€ redis/ # Redis็ผ“ๅญ˜ๆœๅŠก +โ”‚ โ”‚ โ”œโ”€โ”€ login_core/ # ็™ปๅฝ•ๆ ธๅฟƒๆœๅŠก +โ”‚ โ”‚ โ”œโ”€โ”€ admin_core/ # ็ฎก็†ๅ‘˜ๆ ธๅฟƒๆœๅŠก +โ”‚ โ”‚ โ”œโ”€โ”€ zulip_core/ # Zulipๆ ธๅฟƒๆœๅŠก +โ”‚ โ”‚ โ”œโ”€โ”€ security_core/ # ๅฎ‰ๅ…จๆ ธๅฟƒๆœๅŠก +โ”‚ โ”‚ โ””โ”€โ”€ utils/ # ๅทฅๅ…ทๆœๅŠก +โ”‚ โ”œโ”€โ”€ app.module.ts # ๅบ”็”จไธปๆจกๅ— +โ”‚ โ””โ”€โ”€ main.ts # ๅบ”็”จๅ…ฅๅฃ +โ”œโ”€โ”€ client/ # ๐ŸŽจ ๅ‰็ซฏ็ฎก็†็•Œ้ข +โ”œโ”€โ”€ docs/ # ๐Ÿ“š ้กน็›ฎๆ–‡ๆกฃ +โ”œโ”€โ”€ test/ # ๐Ÿงช ๆต‹่ฏ•ๆ–‡ไปถ +โ””โ”€โ”€ config/ # โš™๏ธ ้…็ฝฎๆ–‡ไปถ +``` + +### ็ฝ‘ๅ…ณๅฑ‚็ป“ๆž„ + +``` +src/gateway/ +โ”œโ”€โ”€ auth/ # ่ฎค่ฏ็ฝ‘ๅ…ณ +โ”‚ โ”œโ”€โ”€ login.controller.ts +โ”‚ โ”œโ”€โ”€ register.controller.ts +โ”‚ โ”œโ”€โ”€ jwt_auth.guard.ts +โ”‚ โ”œโ”€โ”€ current_user.decorator.ts +โ”‚ โ”œโ”€โ”€ dto/ +โ”‚ โ””โ”€โ”€ auth.gateway.module.ts +โ””โ”€โ”€ location_broadcast/ # ไฝ็ฝฎๅนฟๆ’ญ็ฝ‘ๅ…ณ + โ”œโ”€โ”€ location_broadcast.gateway.ts + โ””โ”€โ”€ location_broadcast.gateway.module.ts +``` + +### ไธšๅŠกๅฑ‚็ป“ๆž„ ``` src/business/ -โ”œโ”€โ”€ ๐Ÿ“‚ auth/ # ๐Ÿ” ็”จๆˆท่ฎค่ฏๆจกๅ— -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ auth.module.ts # ๆจกๅ—ๅฎšไน‰ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ controllers/ # ๆŽงๅˆถๅ™จ -โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿ“„ login.controller.ts # ็™ปๅฝ•ๆŽฅๅฃๆŽงๅˆถๅ™จ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ services/ # ไธšๅŠกๆœๅŠก -โ”‚ โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ login.service.ts # ็™ปๅฝ•ไธšๅŠก้€ป่พ‘ -โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿ“„ login.service.spec.ts # ็™ปๅฝ•ๆœๅŠกๆต‹่ฏ• -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ dto/ # ๆ•ฐๆฎไผ ่พ“ๅฏน่ฑก -โ”‚ โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ login.dto.ts # ็™ปๅฝ•่ฏทๆฑ‚DTO -โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿ“„ login_response.dto.ts # ็™ปๅฝ•ๅ“ๅบ”DTO -โ”‚ โ””โ”€โ”€ ๐Ÿ“‚ guards/ # ๆƒ้™ๅฎˆๅซ๏ผˆ้ข„็•™๏ผ‰ -โ”‚ -โ”œโ”€โ”€ ๐Ÿ“‚ user-mgmt/ # ๐Ÿ‘ฅ ็”จๆˆท็ฎก็†ๆจกๅ— -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ user-mgmt.module.ts # ๆจกๅ—ๅฎšไน‰ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ controllers/ # ๆŽงๅˆถๅ™จ -โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿ“„ user-status.controller.ts # ็”จๆˆท็Šถๆ€็ฎก็†ๆŽฅๅฃ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ services/ # ไธšๅŠกๆœๅŠก -โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿ“„ user-management.service.ts # ็”จๆˆท็ฎก็†้€ป่พ‘ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ dto/ # ๆ•ฐๆฎไผ ่พ“ๅฏน่ฑก -โ”‚ โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ user-status.dto.ts # ็”จๆˆท็Šถๆ€DTO -โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿ“„ user-status-response.dto.ts # ็Šถๆ€ๅ“ๅบ”DTO -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ enums/ # ๆžšไธพๅฎšไน‰ -โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿ“„ user-status.enum.ts # ็”จๆˆท็Šถๆ€ๆžšไธพ -โ”‚ โ””โ”€โ”€ ๐Ÿ“‚ tests/ # ๆต‹่ฏ•ๆ–‡ไปถ๏ผˆ้ข„็•™๏ผ‰ -โ”‚ -โ”œโ”€โ”€ ๐Ÿ“‚ admin/ # ๐Ÿ›ก๏ธ ็ฎก็†ๅ‘˜ๆจกๅ— -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ admin.controller.ts # ็ฎก็†ๅ‘˜ๆŽฅๅฃ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ admin.service.ts # ็ฎก็†ๅ‘˜ไธšๅŠก้€ป่พ‘ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ admin.module.ts # ๆจกๅ—ๅฎšไน‰ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ admin.service.spec.ts # ็ฎก็†ๅ‘˜ๆœๅŠกๆต‹่ฏ• -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ dto/ # ๆ•ฐๆฎไผ ่พ“ๅฏน่ฑก -โ”‚ โ””โ”€โ”€ ๐Ÿ“‚ guards/ # ๆƒ้™ๅฎˆๅซ -โ”‚ -โ”œโ”€โ”€ ๐Ÿ“‚ zulip/ # ๐Ÿ’ฌ Zulip้›†ๆˆๆจกๅ— -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ zulip.service.ts # ZulipไธšๅŠกๆœๅŠก -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ zulip_websocket.gateway.ts # WebSocket็ฝ‘ๅ…ณ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ zulip.module.ts # ๆจกๅ—ๅฎšไน‰ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ interfaces/ # ๆŽฅๅฃๅฎšไน‰ -โ”‚ โ””โ”€โ”€ ๐Ÿ“‚ services/ # ๅญๆœๅŠก -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ message_filter.service.ts # ๆถˆๆฏ่ฟ‡ๆปค -โ”‚ โ””โ”€โ”€ ๐Ÿ“„ session_cleanup.service.ts # ไผš่ฏๆธ…็† -โ”‚ -โ””โ”€โ”€ ๐Ÿ“‚ shared/ # ๐Ÿ”— ๅ…ฑไบซไธšๅŠก็ป„ไปถ - โ”œโ”€โ”€ ๐Ÿ“‚ dto/ # ๅ…ฑไบซๆ•ฐๆฎไผ ่พ“ๅฏน่ฑก - โ””โ”€โ”€ ๐Ÿ“„ index.ts # ๅฏผๅ‡บๆ–‡ไปถ +โ”œโ”€โ”€ auth/ # ็”จๆˆท่ฎค่ฏไธšๅŠก +โ”‚ โ”œโ”€โ”€ login.service.ts +โ”‚ โ”œโ”€โ”€ register.service.ts +โ”‚ โ””โ”€โ”€ auth.module.ts +โ”œโ”€โ”€ user_mgmt/ # ็”จๆˆท็ฎก็†ไธšๅŠก +โ”‚ โ”œโ”€โ”€ user_management.service.ts +โ”‚ โ”œโ”€โ”€ dto/ +โ”‚ โ”œโ”€โ”€ enums/ +โ”‚ โ””โ”€โ”€ user_mgmt.module.ts +โ”œโ”€โ”€ admin/ # ็ฎก็†ๅ‘˜ไธšๅŠก +โ”‚ โ”œโ”€โ”€ admin.service.ts +โ”‚ โ””โ”€โ”€ admin.module.ts +โ”œโ”€โ”€ zulip/ # Zulip้›†ๆˆไธšๅŠก +โ”‚ โ”œโ”€โ”€ zulip.service.ts +โ”‚ โ”œโ”€โ”€ services/ +โ”‚ โ””โ”€โ”€ zulip.module.ts +โ”œโ”€โ”€ location_broadcast/ # ไฝ็ฝฎๅนฟๆ’ญไธšๅŠก +โ”‚ โ”œโ”€โ”€ location_broadcast.service.ts +โ”‚ โ””โ”€โ”€ location_broadcast.module.ts +โ””โ”€โ”€ notice/ # ๅ…ฌๅ‘ŠไธšๅŠก + โ”œโ”€โ”€ notice.service.ts + โ””โ”€โ”€ notice.module.ts ``` -### โš™๏ธ ๆ ธๅฟƒๆŠ€ๆœฏๆœๅŠก (`src/core/`) - -> **่ฎพ่ฎกๅŽŸๅˆ™**: ๆไพ›ๆŠ€ๆœฏๅŸบ็ก€่ฎพๆ–ฝ๏ผŒๆ”ฏๆŒไธšๅŠกๆจกๅ—่ฟ่กŒ +### ๆ ธๅฟƒๅฑ‚็ป“ๆž„ ``` src/core/ -โ”œโ”€โ”€ ๐Ÿ“‚ db/ # ๐Ÿ—„๏ธ ๆ•ฐๆฎๅบ“ๅฑ‚ -โ”‚ โ””โ”€โ”€ ๐Ÿ“‚ users/ # ็”จๆˆทๆ•ฐๆฎๆœๅŠก -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ users.service.ts # MySQLๆ•ฐๆฎๅบ“ๅฎž็Žฐ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ users_memory.service.ts # ๅ†…ๅญ˜ๆ•ฐๆฎๅบ“ๅฎž็Žฐ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ users.dto.ts # ็”จๆˆทๆ•ฐๆฎไผ ่พ“ๅฏน่ฑก -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ users.entity.ts # ็”จๆˆทๅฎžไฝ“ๅฎšไน‰ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ users.module.ts # ็”จๆˆทๆ•ฐๆฎๆจกๅ— -โ”‚ โ””โ”€โ”€ ๐Ÿ“„ users.service.spec.ts # ็”จๆˆทๆœๅŠกๆต‹่ฏ• -โ”‚ -โ”œโ”€โ”€ ๐Ÿ“‚ redis/ # ๐Ÿ”ด Redis็ผ“ๅญ˜ๅฑ‚ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ redis.module.ts # Redisๆจกๅ— -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ real_redis.service.ts # Redis็œŸๅฎžๅฎž็Žฐ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ file_redis.service.ts # ๆ–‡ไปถๅญ˜ๅ‚จๅฎž็Žฐ -โ”‚ โ””โ”€โ”€ ๐Ÿ“„ redis.interface.ts # RedisๆœๅŠกๆŽฅๅฃ -โ”‚ -โ”œโ”€โ”€ ๐Ÿ“‚ login_core/ # ๐Ÿ”‘ ็™ปๅฝ•ๆ ธๅฟƒๆœๅŠก -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ login_core.service.ts # ็™ปๅฝ•ๆ ธๅฟƒ้€ป่พ‘ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ login_core.module.ts # ๆจกๅ—ๅฎšไน‰ -โ”‚ โ””โ”€โ”€ ๐Ÿ“„ login_core.service.spec.ts # ็™ปๅฝ•ๆ ธๅฟƒๆต‹่ฏ• -โ”‚ -โ”œโ”€โ”€ ๐Ÿ“‚ admin_core/ # ๐Ÿ‘‘ ็ฎก็†ๅ‘˜ๆ ธๅฟƒๆœๅŠก -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ admin_core.service.ts # ็ฎก็†ๅ‘˜ๆ ธๅฟƒ้€ป่พ‘ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ admin_core.module.ts # ๆจกๅ—ๅฎšไน‰ -โ”‚ โ””โ”€โ”€ ๐Ÿ“„ admin_core.service.spec.ts # ็ฎก็†ๅ‘˜ๆ ธๅฟƒๆต‹่ฏ• -โ”‚ -โ”œโ”€โ”€ ๐Ÿ“‚ zulip_core/ # ๐Ÿ’ฌ Zulipๆ ธๅฟƒๆœๅŠก -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ zulip_core.module.ts # Zulipๆ ธๅฟƒๆจกๅ— -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ config/ # ้…็ฝฎๆ–‡ไปถ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ interfaces/ # ๆŽฅๅฃๅฎšไน‰ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ services/ # ๆ ธๅฟƒๆœๅŠก -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ types/ # ็ฑปๅž‹ๅฎšไน‰ -โ”‚ โ””โ”€โ”€ ๐Ÿ“„ index.ts # ๅฏผๅ‡บๆ–‡ไปถ -โ”‚ -โ”œโ”€โ”€ ๐Ÿ“‚ security_core/ # ๐Ÿ›ก๏ธ ๅฎ‰ๅ…จๆ ธๅฟƒๆจกๅ— -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ security_core.module.ts # ๅฎ‰ๅ…จๆจกๅ—ๅฎšไน‰ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ guards/ # ๅฎ‰ๅ…จๅฎˆๅซ -โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿ“„ throttle.guard.ts # ้ข‘็އ้™ๅˆถๅฎˆๅซ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ interceptors/ # ๆ‹ฆๆˆชๅ™จ -โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿ“„ timeout.interceptor.ts # ่ถ…ๆ—ถๆ‹ฆๆˆชๅ™จ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ middleware/ # ไธญ้—ดไปถ -โ”‚ โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ maintenance.middleware.ts # ็ปดๆŠคๆจกๅผไธญ้—ดไปถ -โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿ“„ content_type.middleware.ts # ๅ†…ๅฎน็ฑปๅž‹ไธญ้—ดไปถ -โ”‚ โ””โ”€โ”€ ๐Ÿ“‚ decorators/ # ่ฃ…้ฅฐๅ™จ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ throttle.decorator.ts # ้ข‘็އ้™ๅˆถ่ฃ…้ฅฐๅ™จ -โ”‚ โ””โ”€โ”€ ๐Ÿ“„ timeout.decorator.ts # ่ถ…ๆ—ถ่ฃ…้ฅฐๅ™จ -โ”‚ -โ””โ”€โ”€ ๐Ÿ“‚ utils/ # ๐Ÿ› ๏ธ ๅทฅๅ…ทๆœๅŠก - โ”œโ”€โ”€ ๐Ÿ“‚ email/ # ๐Ÿ“ง ้‚ฎไปถๆœๅŠก - โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ email.service.ts # ้‚ฎไปถๅ‘้€ๆœๅŠก - โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ email.module.ts # ้‚ฎไปถๆจกๅ— - โ”‚ โ””โ”€โ”€ ๐Ÿ“„ email.service.spec.ts # ้‚ฎไปถๆœๅŠกๆต‹่ฏ• - โ”œโ”€โ”€ ๐Ÿ“‚ verification/ # ๐Ÿ”ข ้ชŒ่ฏ็ ๆœๅŠก - โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ verification.service.ts # ้ชŒ่ฏ็ ็”Ÿๆˆ้ชŒ่ฏ - โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ verification.module.ts # ้ชŒ่ฏ็ ๆจกๅ— - โ”‚ โ””โ”€โ”€ ๐Ÿ“„ verification.service.spec.ts # ้ชŒ่ฏ็ ๆœๅŠกๆต‹่ฏ• - โ””โ”€โ”€ ๐Ÿ“‚ logger/ # ๐Ÿ“ ๆ—ฅๅฟ—ๆœๅŠก - โ”œโ”€โ”€ ๐Ÿ“„ logger.service.ts # ๆ—ฅๅฟ—่ฎฐๅฝ•ๆœๅŠก - โ”œโ”€โ”€ ๐Ÿ“„ logger.module.ts # ๆ—ฅๅฟ—ๆจกๅ— - โ”œโ”€โ”€ ๐Ÿ“„ logger.config.ts # ๆ—ฅๅฟ—้…็ฝฎ - โ””โ”€โ”€ ๐Ÿ“„ log_management.service.ts # ๆ—ฅๅฟ—็ฎก็†ๆœๅŠก -``` - -### ๐ŸŽจ ๅ‰็ซฏ็ฎก็†็•Œ้ข (`client/`) - -> **่ฎพ่ฎกๅŽŸๅˆ™**: ็‹ฌ็ซ‹็š„ๅ‰็ซฏ้กน็›ฎ๏ผŒๆไพ›็ฎก็†ๅ‘˜ๅŽๅฐๅŠŸ่ƒฝ๏ผŒๅŸบไบŽReact + Vite + Ant Design - -``` -client/ -โ”œโ”€โ”€ ๐Ÿ“‚ src/ # ๅ‰็ซฏๆบ็  -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ app/ # ๅบ”็”จ็ป„ไปถ -โ”‚ โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ App.tsx # ๅบ”็”จไธป็ป„ไปถ -โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿ“„ AdminLayout.tsx # ็ฎก็†ๅ‘˜ๅธƒๅฑ€็ป„ไปถ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ pages/ # ้กต้ข็ป„ไปถ -โ”‚ โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ LoginPage.tsx # ็™ปๅฝ•้กต้ข -โ”‚ โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ UsersPage.tsx # ็”จๆˆท็ฎก็†้กต้ข -โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿ“„ LogsPage.tsx # ๆ—ฅๅฟ—็ฎก็†้กต้ข -โ”‚ โ”œโ”€โ”€ ๐Ÿ“‚ lib/ # ๅทฅๅ…ทๅบ“ -โ”‚ โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ api.ts # APIๅฎขๆˆท็ซฏ -โ”‚ โ”‚ โ””โ”€โ”€ ๐Ÿ“„ adminAuth.ts # ็ฎก็†ๅ‘˜่ฎค่ฏๆœๅŠก -โ”‚ โ””โ”€โ”€ ๐Ÿ“„ main.tsx # ๅบ”็”จๅ…ฅๅฃ -โ”œโ”€โ”€ ๐Ÿ“‚ dist/ # ๆž„ๅปบไบง็‰ฉ -โ”œโ”€โ”€ ๐Ÿ“„ package.json # ๅ‰็ซฏไพ่ต– -โ”œโ”€โ”€ ๐Ÿ“„ vite.config.ts # Vite้…็ฝฎ -โ””โ”€โ”€ ๐Ÿ“„ tsconfig.json # TypeScript้…็ฝฎ -``` - -### ๐Ÿ“š ๆ–‡ๆกฃไธญๅฟƒ (`docs/`) - -> **่ฎพ่ฎกๅŽŸๅˆ™**: ๅฎŒๆ•ด็š„้กน็›ฎๆ–‡ๆกฃ๏ผŒๆ”ฏๆŒๅผ€ๅ‘่€…ๅฟซ้€ŸไธŠๆ‰‹ - -``` -docs/ -โ”œโ”€โ”€ ๐Ÿ“„ README.md # ๐Ÿ“– ๆ–‡ๆกฃๅฏผ่ˆชไธญๅฟƒ -โ”œโ”€โ”€ ๐Ÿ“„ ARCHITECTURE.md # ๐Ÿ—๏ธ ๆžถๆž„่ฎพ่ฎกๆ–‡ๆกฃ -โ”œโ”€โ”€ ๐Ÿ“„ CONTRIBUTORS.md # ๐Ÿค ่ดก็Œฎ่€…ๆŒ‡ๅ— -โ”‚ -โ”œโ”€โ”€ ๐Ÿ“‚ api/ # ๐Ÿ”Œ APIๆŽฅๅฃๆ–‡ๆกฃ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ README.md # APIๆ–‡ๆกฃไฝฟ็”จๆŒ‡ๅ— -โ”‚ โ””โ”€โ”€ ๐Ÿ“„ api-documentation.md # ๅฎŒๆ•ดAPIๆŽฅๅฃๆ–‡ๆกฃ -โ”‚ -โ”œโ”€โ”€ ๐Ÿ“‚ development/ # ๐Ÿ’ป ๅผ€ๅ‘ๆŒ‡ๅ— -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ backend_development_guide.md # ๅŽ็ซฏๅผ€ๅ‘่ง„่Œƒ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ git_commit_guide.md # Gitๆไบค่ง„่Œƒ -โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ AI่พ…ๅŠฉๅผ€ๅ‘่ง„่ŒƒๆŒ‡ๅ—.md # AI่พ…ๅŠฉๅผ€ๅ‘ๆŒ‡ๅ— -โ”‚ โ””โ”€โ”€ ๐Ÿ“„ TESTING.md # ๆต‹่ฏ•ๆŒ‡ๅ— -โ”‚ -โ””โ”€โ”€ ๐Ÿ“‚ deployment/ # ๐Ÿš€ ้ƒจ็ฝฒๆ–‡ๆกฃ - โ””โ”€โ”€ ๐Ÿ“„ DEPLOYMENT.md # ็”Ÿไบง็Žฏๅขƒ้ƒจ็ฝฒๆŒ‡ๅ— -``` - -### ๐Ÿงช ๆต‹่ฏ•ๆ–‡ไปถ (`test/`) - -> **่ฎพ่ฎกๅŽŸๅˆ™**: ๅฎŒๆ•ด็š„ๆต‹่ฏ•่ฆ†็›–๏ผŒ็กฎไฟไปฃ็ ่ดจ้‡ - -``` -test/ -โ”œโ”€โ”€ ๐Ÿ“‚ unit/ # ๅ•ๅ…ƒๆต‹่ฏ• -โ”œโ”€โ”€ ๐Ÿ“‚ integration/ # ้›†ๆˆๆต‹่ฏ• -โ”œโ”€โ”€ ๐Ÿ“‚ e2e/ # ็ซฏๅˆฐ็ซฏๆต‹่ฏ• -โ””โ”€โ”€ ๐Ÿ“‚ fixtures/ # ๆต‹่ฏ•ๆ•ฐๆฎ -``` - -### โš™๏ธ ้…็ฝฎๆ–‡ไปถ - -> **่ฎพ่ฎกๅŽŸๅˆ™**: ๆธ…ๆ™ฐ็š„้…็ฝฎ็ฎก็†๏ผŒๆ”ฏๆŒๅคš็Žฏๅขƒ้ƒจ็ฝฒ - -``` -้กน็›ฎๆ น็›ฎๅฝ•/ -โ”œโ”€โ”€ ๐Ÿ“„ .env # ๐Ÿ”ง ็Žฏๅขƒๅ˜้‡้…็ฝฎ -โ”œโ”€โ”€ ๐Ÿ“„ .env.example # ๐Ÿ”ง ็Žฏๅขƒๅ˜้‡็คบไพ‹ -โ”œโ”€โ”€ ๐Ÿ“„ .env.production.example # ๐Ÿ”ง ็”Ÿไบง็Žฏๅขƒ็คบไพ‹ -โ”œโ”€โ”€ ๐Ÿ“„ package.json # ๐Ÿ“‹ ๅŽ็ซฏ้กน็›ฎไพ่ต–้…็ฝฎ -โ”œโ”€โ”€ ๐Ÿ“„ pnpm-workspace.yaml # ๐Ÿ“ฆ pnpmๅทฅไฝœ็ฉบ้—ด้…็ฝฎ -โ”œโ”€โ”€ ๐Ÿ“„ tsconfig.json # ๐Ÿ“˜ TypeScript้…็ฝฎ -โ”œโ”€โ”€ ๐Ÿ“„ jest.config.js # ๐Ÿงช Jestๆต‹่ฏ•้…็ฝฎ -โ”œโ”€โ”€ ๐Ÿ“„ nest-cli.json # ๐Ÿ  NestJS CLI้…็ฝฎ -โ””โ”€โ”€ ๐Ÿ“„ ecosystem.config.js # ๐Ÿš€ PM2่ฟ›็จ‹็ฎก็†้…็ฝฎ - -client/ -โ”œโ”€โ”€ ๐Ÿ“„ package.json # ๐Ÿ“‹ ๅ‰็ซฏ้กน็›ฎไพ่ต–้…็ฝฎ -โ”œโ”€โ”€ ๐Ÿ“„ vite.config.ts # โšก Viteๆž„ๅปบ้…็ฝฎ -โ””โ”€โ”€ ๐Ÿ“„ tsconfig.json # ๐Ÿ“˜ ๅ‰็ซฏTypeScript้…็ฝฎ +โ”œโ”€โ”€ db/users/ # ็”จๆˆทๆ•ฐๆฎๆœๅŠก +โ”‚ โ”œโ”€โ”€ users.service.ts # MySQLๅฎž็Žฐ +โ”‚ โ”œโ”€โ”€ users_memory.service.ts # ๅ†…ๅญ˜ๅฎž็Žฐ +โ”‚ โ”œโ”€โ”€ users.entity.ts +โ”‚ โ””โ”€โ”€ users.module.ts +โ”œโ”€โ”€ redis/ # Redis็ผ“ๅญ˜ๆœๅŠก +โ”‚ โ”œโ”€โ”€ real_redis.service.ts +โ”‚ โ”œโ”€โ”€ file_redis.service.ts +โ”‚ โ””โ”€โ”€ redis.module.ts +โ”œโ”€โ”€ login_core/ # ็™ปๅฝ•ๆ ธๅฟƒๆœๅŠก +โ”‚ โ”œโ”€โ”€ login_core.service.ts +โ”‚ โ””โ”€โ”€ login_core.module.ts +โ”œโ”€โ”€ admin_core/ # ็ฎก็†ๅ‘˜ๆ ธๅฟƒๆœๅŠก +โ”‚ โ”œโ”€โ”€ admin_core.service.ts +โ”‚ โ””โ”€โ”€ admin_core.module.ts +โ”œโ”€โ”€ zulip_core/ # Zulipๆ ธๅฟƒๆœๅŠก +โ”‚ โ”œโ”€โ”€ services/ +โ”‚ โ”œโ”€โ”€ config/ +โ”‚ โ””โ”€โ”€ zulip_core.module.ts +โ”œโ”€โ”€ security_core/ # ๅฎ‰ๅ…จๆ ธๅฟƒๆœๅŠก +โ”‚ โ”œโ”€โ”€ guards/ +โ”‚ โ”œโ”€โ”€ interceptors/ +โ”‚ โ”œโ”€โ”€ middleware/ +โ”‚ โ””โ”€โ”€ security_core.module.ts +โ””โ”€โ”€ utils/ # ๅทฅๅ…ทๆœๅŠก + โ”œโ”€โ”€ email/ + โ”œโ”€โ”€ verification/ + โ””โ”€โ”€ logger/ ``` --- -## ๐Ÿ—๏ธ ๅˆ†ๅฑ‚ๆžถๆž„่ฎพ่ฎก +## ๅŒๆจกๅผๆžถๆž„ -### ๐Ÿ“Š ๆžถๆž„ๅˆ†ๅฑ‚่ฏดๆ˜Ž +### ๆจกๅผๅฏนๆฏ” -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ๐ŸŒ ่กจ็Žฐๅฑ‚ (Presentation) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Controllers โ”‚ โ”‚ WebSocket โ”‚ โ”‚ Swagger UI โ”‚ โ”‚ -โ”‚ โ”‚ (HTTPๆŽฅๅฃ) โ”‚ โ”‚ Gateways โ”‚ โ”‚ (APIๆ–‡ๆกฃ) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โฌ‡๏ธ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ๐ŸŽฏ ไธšๅŠกๅฑ‚ (Business) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Auth Module โ”‚ โ”‚ UserMgmt โ”‚ โ”‚ Admin Module โ”‚ โ”‚ -โ”‚ โ”‚ (็”จๆˆท่ฎค่ฏ) โ”‚ โ”‚ Module โ”‚ โ”‚ (็ฎก็†ๅ‘˜) โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ (็”จๆˆท็ฎก็†) โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Security Module โ”‚ โ”‚ Zulip Module โ”‚ โ”‚ Shared Module โ”‚ โ”‚ -โ”‚ โ”‚ (ๅฎ‰ๅ…จ้˜ฒๆŠค) โ”‚ โ”‚ (Zulip้›†ๆˆ) โ”‚ โ”‚ (ๅ…ฑไบซ็ป„ไปถ) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โฌ‡๏ธ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ โš™๏ธ ๆœๅŠกๅฑ‚ (Service) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Login Core โ”‚ โ”‚ Admin Core โ”‚ โ”‚ Zulip Core โ”‚ โ”‚ -โ”‚ โ”‚ (็™ปๅฝ•ๆ ธๅฟƒ) โ”‚ โ”‚ (็ฎก็†ๅ‘˜ๆ ธๅฟƒ) โ”‚ โ”‚ (Zulipๆ ธๅฟƒ) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Email Service โ”‚ โ”‚ Verification โ”‚ โ”‚ Logger Service โ”‚ โ”‚ -โ”‚ โ”‚ (้‚ฎไปถๆœๅŠก) โ”‚ โ”‚ Service โ”‚ โ”‚ (ๆ—ฅๅฟ—ๆœๅŠก) โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ (้ชŒ่ฏ็ ๆœๅŠก) โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โฌ‡๏ธ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ๐Ÿ—„๏ธ ๆ•ฐๆฎๅฑ‚ (Data) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Users Service โ”‚ โ”‚ Redis Service โ”‚ โ”‚ File Storage โ”‚ โ”‚ -โ”‚ โ”‚ (็”จๆˆทๆ•ฐๆฎ) โ”‚ โ”‚ (็ผ“ๅญ˜ๆœๅŠก) โ”‚ โ”‚ (ๆ–‡ไปถๅญ˜ๅ‚จ) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ MySQL/Memory โ”‚ โ”‚ Redis/File โ”‚ โ”‚ Logs/Data โ”‚ โ”‚ -โ”‚ โ”‚ (ๆ•ฐๆฎๅบ“) โ”‚ โ”‚ (็ผ“ๅญ˜ๅฎž็Žฐ) โ”‚ โ”‚ (ๆ—ฅๅฟ—ๆ•ฐๆฎ) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` +| ๅŠŸ่ƒฝ | ๅผ€ๅ‘ๆจกๅผ | ็”Ÿไบงๆจกๅผ | +|------|---------|---------| +| ๆ•ฐๆฎๅบ“ | ๅ†…ๅญ˜ๅญ˜ๅ‚จ | MySQL | +| ็ผ“ๅญ˜ | ๆ–‡ไปถๅญ˜ๅ‚จ | Redis | +| ้‚ฎไปถ | ๆŽงๅˆถๅฐ่พ“ๅ‡บ | SMTPๆœๅŠกๅ™จ | +| ๆ—ฅๅฟ— | ๆŽงๅˆถๅฐ+ๆ–‡ไปถ | ็ป“ๆž„ๅŒ–ๆ—ฅๅฟ— | -### ๐Ÿ”„ ๆ•ฐๆฎๆตๅ‘ +### ้…็ฝฎ็คบไพ‹ -#### ็”จๆˆท็™ปๅฝ•ๆต็จ‹็คบไพ‹ - -``` -1. ๐Ÿ“ฑ ็”จๆˆท่ฏทๆฑ‚ โ†’ LoginController.login() -2. ๐Ÿ” ๅ‚ๆ•ฐ้ชŒ่ฏ โ†’ class-validator่ฃ…้ฅฐๅ™จ -3. ๐ŸŽฏ ไธšๅŠก้€ป่พ‘ โ†’ LoginService.login() -4. โš™๏ธ ๆ ธๅฟƒๆœๅŠก โ†’ LoginCoreService.validateUser() -5. ๐Ÿ“ง ๅ‘้€้ชŒ่ฏ็  โ†’ VerificationService.generate() -6. ๐Ÿ’พ ๅญ˜ๅ‚จๆ•ฐๆฎ โ†’ UsersService.findByEmail() + RedisService.set() -7. ๐Ÿ“ ่ฎฐๅฝ•ๆ—ฅๅฟ— โ†’ LoggerService.log() -8. โœ… ่ฟ”ๅ›žๅ“ๅบ” โ†’ ็”จๆˆทๆ”ถๅˆฐ็™ปๅฝ•็ป“ๆžœ -``` - -#### ็ฎก็†ๅ‘˜ๆ“ไฝœๆต็จ‹็คบไพ‹ - -``` -1. ๐Ÿ›ก๏ธ ็ฎก็†ๅ‘˜่ฏทๆฑ‚ โ†’ AdminController.resetUserPassword() -2. ๐Ÿ” ๆƒ้™้ชŒ่ฏ โ†’ AdminGuard.canActivate() -3. ๐ŸŽฏ ไธšๅŠก้€ป่พ‘ โ†’ AdminService.resetPassword() -4. โš™๏ธ ๆ ธๅฟƒๆœๅŠก โ†’ AdminCoreService.resetUserPassword() -5. ๐Ÿ”‘ ๅฏ†็ ๅŠ ๅฏ† โ†’ bcrypt.hash() -6. ๐Ÿ’พ ๆ›ดๆ–ฐๆ•ฐๆฎ โ†’ UsersService.update() -7. ๐Ÿ“ง ้€š็Ÿฅ็”จๆˆท โ†’ EmailService.sendPasswordReset() -8. ๐Ÿ“ ๅฎก่ฎกๆ—ฅๅฟ— โ†’ LoggerService.audit() -9. โœ… ่ฟ”ๅ›žๅ“ๅบ” โ†’ ็ฎก็†ๅ‘˜ๆ”ถๅˆฐๆ“ไฝœ็ป“ๆžœ -``` - ---- - -## ๐Ÿ”„ ๅŒๆจกๅผๆžถๆž„ - -### ๐ŸŽฏ ่ฎพ่ฎก็›ฎๆ ‡ - -- **ๅผ€ๅ‘ๆต‹่ฏ•**: ้›ถไพ่ต–ๅฟซ้€ŸๅฏๅŠจ๏ผŒๆ— ้œ€ๅฎ‰่ฃ…MySQLใ€Redis็ญ‰ๅค–้ƒจๆœๅŠก -- **็”Ÿไบง้ƒจ็ฝฒ**: ้ซ˜ๆ€ง่ƒฝใ€้ซ˜ๅฏ็”จ๏ผŒๆ”ฏๆŒ้›†็พคๅ’Œ่ดŸ่ฝฝๅ‡่กก - -### ๐Ÿ“Š ๆจกๅผๅฏนๆฏ” - -| ๅŠŸ่ƒฝๆจกๅ— | ๐Ÿงช ๅผ€ๅ‘ๆต‹่ฏ•ๆจกๅผ | ๐Ÿš€ ็”Ÿไบง้ƒจ็ฝฒๆจกๅผ | -|----------|----------------|----------------| -| **ๆ•ฐๆฎๅบ“** | ๅ†…ๅญ˜ๅญ˜ๅ‚จ (UsersMemoryService) | MySQL (UsersService + TypeORM) | -| **็ผ“ๅญ˜** | ๆ–‡ไปถๅญ˜ๅ‚จ (FileRedisService) | Redis (RealRedisService + IORedis) | -| **้‚ฎไปถ** | ๆŽงๅˆถๅฐ่พ“ๅ‡บ (ๆต‹่ฏ•ๆจกๅผ) | SMTPๆœๅŠกๅ™จ (็”Ÿไบงๆจกๅผ) | -| **ๆ—ฅๅฟ—** | ๆŽงๅˆถๅฐ + ๆ–‡ไปถ | ็ป“ๆž„ๅŒ–ๆ—ฅๅฟ— + ๆ—ฅๅฟ—่ฝฎ่ฝฌ | -| **้…็ฝฎ** | `.env` ้ป˜่ฎค้…็ฝฎ | ็Žฏๅขƒๅ˜้‡ + ้…็ฝฎไธญๅฟƒ | - -### โš™๏ธ ๆจกๅผๅˆ‡ๆข้…็ฝฎ - -#### ๅผ€ๅ‘ๆต‹่ฏ•ๆจกๅผ (.env) +**ๅผ€ๅ‘ๆจกๅผ๏ผš** ```bash -# ๆ•ฐๆฎๅญ˜ๅ‚จๆจกๅผ -USE_FILE_REDIS=true # ไฝฟ็”จๆ–‡ไปถๅญ˜ๅ‚จไปฃๆ›ฟRedis -NODE_ENV=development # ๅผ€ๅ‘็Žฏๅขƒ - -# ๆ•ฐๆฎๅบ“้…็ฝฎ๏ผˆๆณจ้‡Šๆމ๏ผŒไฝฟ็”จๅ†…ๅญ˜ๆ•ฐๆฎๅบ“๏ผ‰ -# DB_HOST=localhost -# DB_USERNAME=root -# DB_PASSWORD=password - -# ้‚ฎไปถ้…็ฝฎ๏ผˆๆณจ้‡Šๆމ๏ผŒไฝฟ็”จๆต‹่ฏ•ๆจกๅผ๏ผ‰ -# EMAIL_HOST=smtp.gmail.com -# EMAIL_USER=your_email@gmail.com -# EMAIL_PASS=your_password +USE_FILE_REDIS=true +NODE_ENV=development +# ๆ— ้œ€้…็ฝฎๆ•ฐๆฎๅบ“ๅ’Œ้‚ฎไปถ ``` -#### ็”Ÿไบง้ƒจ็ฝฒๆจกๅผ (.env.production) +**็”Ÿไบงๆจกๅผ๏ผš** ```bash -# ๆ•ฐๆฎๅญ˜ๅ‚จๆจกๅผ -USE_FILE_REDIS=false # ไฝฟ็”จ็œŸๅฎžRedis -NODE_ENV=production # ็”Ÿไบง็Žฏๅขƒ - -# ๆ•ฐๆฎๅบ“้…็ฝฎ +USE_FILE_REDIS=false +NODE_ENV=production DB_HOST=your_mysql_host -DB_PORT=3306 -DB_USERNAME=your_username -DB_PASSWORD=your_password -DB_DATABASE=whale_town - -# Redis้…็ฝฎ REDIS_HOST=your_redis_host -REDIS_PORT=6379 -REDIS_PASSWORD=your_redis_password - -# ้‚ฎไปถ้…็ฝฎ -EMAIL_HOST=smtp.gmail.com -EMAIL_PORT=587 -EMAIL_USER=your_email@gmail.com -EMAIL_PASS=your_app_password +EMAIL_HOST=smtp.163.com ``` -### ๐Ÿ”ง ๅฎž็Žฐๆœบๅˆถ +### ๅฎž็Žฐๆœบๅˆถ + +้€š่ฟ‡ไพ่ต–ๆณจๅ…ฅๅ’ŒๅทฅๅŽ‚ๆจกๅผๅฎž็Žฐ่‡ชๅŠจๅˆ‡ๆข๏ผš -#### ไพ่ต–ๆณจๅ…ฅๅˆ‡ๆข ```typescript -// redis.module.ts @Module({ providers: [ { provide: 'IRedisService', - useFactory: (configService: ConfigService) => { - const useFileRedis = configService.get('USE_FILE_REDIS'); - return useFileRedis - ? new FileRedisService() - : new RealRedisService(configService); + useFactory: (config: ConfigService) => { + return config.get('USE_FILE_REDIS') + ? new FileRedisService() + : new RealRedisService(config); }, inject: [ConfigService], }, @@ -467,220 +318,117 @@ EMAIL_PASS=your_app_password export class RedisModule {} ``` -#### ้…็ฝฎ้ฉฑๅŠจๆœๅŠก้€‰ๆ‹ฉ -```typescript -// users.module.ts -@Module({ - providers: [ - { - provide: 'IUsersService', - useFactory: (configService: ConfigService) => { - const dbHost = configService.get('DB_HOST'); - return dbHost - ? new UsersService() - : new UsersMemoryService(); - }, - inject: [ConfigService], - }, - ], -}) -export class UsersModule {} -``` - --- -## ๐Ÿ“ฆ ๆจกๅ—ไพ่ต–ๅ…ณ็ณป +## ๆจกๅ—ไพ่ต–ๅ…ณ็ณป -### ๐Ÿ—๏ธ ๆจกๅ—ไพ่ต–ๅ›พ +### ไพ่ต–ๆ–นๅ‘ ``` -AppModule (ๅบ”็”จไธปๆจกๅ—) -โ”œโ”€โ”€ ๐Ÿ“Š ConfigModule (ๅ…จๅฑ€้…็ฝฎ) -โ”œโ”€โ”€ ๐Ÿ“ LoggerModule (ๆ—ฅๅฟ—็ณป็ปŸ) -โ”œโ”€โ”€ ๐Ÿ”ด RedisModule (็ผ“ๅญ˜ๆœๅŠก) -โ”‚ โ”œโ”€โ”€ RealRedisService (็œŸๅฎžRedis) -โ”‚ โ””โ”€โ”€ FileRedisService (ๆ–‡ไปถๅญ˜ๅ‚จ) -โ”œโ”€โ”€ ๐Ÿ—„๏ธ UsersModule (็”จๆˆทๆ•ฐๆฎ) -โ”‚ โ”œโ”€โ”€ UsersService (MySQLๆ•ฐๆฎๅบ“) -โ”‚ โ””โ”€โ”€ UsersMemoryService (ๅ†…ๅญ˜ๆ•ฐๆฎๅบ“) -โ”œโ”€โ”€ ๐Ÿ“ง EmailModule (้‚ฎไปถๆœๅŠก) -โ”œโ”€โ”€ ๐Ÿ”ข VerificationModule (้ชŒ่ฏ็ ๆœๅŠก) -โ”œโ”€โ”€ ๐Ÿ”‘ LoginCoreModule (็™ปๅฝ•ๆ ธๅฟƒ) -โ”œโ”€โ”€ ๐Ÿ‘‘ AdminCoreModule (็ฎก็†ๅ‘˜ๆ ธๅฟƒ) -โ”œโ”€โ”€ ๐Ÿ’ฌ ZulipCoreModule (Zulipๆ ธๅฟƒ) -โ”œโ”€โ”€ ๐Ÿ”’ SecurityCoreModule (ๅฎ‰ๅ…จๆ ธๅฟƒ) +Gateway Layer + โ†“ ไพ่ต– +Business Layer + โ†“ ไพ่ต– +Core Layer + โ†“ ไพ่ต– +Data Layer +``` + +### ๆจกๅ—ไพ่ต–ๅ›พ + +``` +AppModule +โ”œโ”€โ”€ ConfigModule (ๅ…จๅฑ€้…็ฝฎ) +โ”œโ”€โ”€ LoggerModule (ๆ—ฅๅฟ—็ณป็ปŸ) +โ”œโ”€โ”€ RedisModule (็ผ“ๅญ˜ๆœๅŠก) +โ”œโ”€โ”€ UsersModule (็”จๆˆทๆ•ฐๆฎ) +โ”œโ”€โ”€ EmailModule (้‚ฎไปถๆœๅŠก) +โ”œโ”€โ”€ VerificationModule (้ชŒ่ฏ็ ๆœๅŠก) +โ”œโ”€โ”€ LoginCoreModule (็™ปๅฝ•ๆ ธๅฟƒ) +โ”œโ”€โ”€ AdminCoreModule (็ฎก็†ๅ‘˜ๆ ธๅฟƒ) +โ”œโ”€โ”€ ZulipCoreModule (Zulipๆ ธๅฟƒ) +โ”œโ”€โ”€ SecurityCoreModule (ๅฎ‰ๅ…จๆ ธๅฟƒ) โ”‚ -โ”œโ”€โ”€ ๐ŸŽฏ ไธšๅŠกๅŠŸ่ƒฝๆจกๅ— -โ”‚ โ”œโ”€โ”€ ๐Ÿ” AuthModule (็”จๆˆท่ฎค่ฏ) -โ”‚ โ”‚ โ””โ”€โ”€ ไพ่ต–: LoginCoreModule, EmailModule, VerificationModule, SecurityCoreModule -โ”‚ โ”œโ”€โ”€ ๐Ÿ‘ฅ UserMgmtModule (็”จๆˆท็ฎก็†) -โ”‚ โ”‚ โ””โ”€โ”€ ไพ่ต–: UsersModule, LoggerModule, SecurityCoreModule -โ”‚ โ”œโ”€โ”€ ๐Ÿ›ก๏ธ AdminModule (็ฎก็†ๅ‘˜) -โ”‚ โ”‚ โ””โ”€โ”€ ไพ่ต–: AdminCoreModule, UsersModule, SecurityCoreModule -โ”‚ โ”œโ”€โ”€ ๐Ÿ’ฌ ZulipModule (Zulip้›†ๆˆ) -โ”‚ โ”‚ โ””โ”€โ”€ ไพ่ต–: ZulipCoreModule, RedisModule -โ”‚ โ””โ”€โ”€ ๐Ÿ”— SharedModule (ๅ…ฑไบซ็ป„ไปถ) -``` - -### ๐Ÿ”„ ๆจกๅ—ไบคไบ’ๆต็จ‹ - -#### ็”จๆˆท่ฎค่ฏๆต็จ‹ -``` -AuthController โ†’ LoginService โ†’ LoginCoreService - โ†“ -EmailService โ† VerificationService โ† RedisService - โ†“ - UsersService -``` - -#### ็ฎก็†ๅ‘˜ๆ“ไฝœๆต็จ‹ -``` -AdminController โ†’ AdminService โ†’ AdminCoreService - โ†“ -LoggerService โ† UsersService โ† RedisService -``` - -#### ๅฎ‰ๅ…จ้˜ฒๆŠคๆต็จ‹ -``` -SecurityGuard โ†’ RedisService (้ข‘็އ้™ๅˆถ) - โ†’ LoggerService (ๅฎก่ฎกๆ—ฅๅฟ—) - โ†’ ConfigService (็ปดๆŠคๆจกๅผ) +โ”œโ”€โ”€ Gateway Layer +โ”‚ โ”œโ”€โ”€ AuthGatewayModule +โ”‚ โ””โ”€โ”€ LocationBroadcastGatewayModule +โ”‚ +โ””โ”€โ”€ Business Layer + โ”œโ”€โ”€ AuthModule + โ”œโ”€โ”€ UserMgmtModule + โ”œโ”€โ”€ AdminModule + โ”œโ”€โ”€ ZulipModule + โ”œโ”€โ”€ LocationBroadcastModule + โ””โ”€โ”€ NoticeModule ``` --- -## ๐Ÿš€ ๆ‰ฉๅฑ•ๆŒ‡ๅ— +## ๆ•ฐๆฎๆตๅ‘ -### ๐Ÿ“ ๆทปๅŠ ๆ–ฐ็š„ไธšๅŠกๆจกๅ— +### ็”จๆˆท็™ปๅฝ•ๆต็จ‹ -#### 1. ๅˆ›ๅปบไธšๅŠกๆจกๅ—็ป“ๆž„ -```bash -# ๅˆ›ๅปบๆจกๅ—็›ฎๅฝ• -mkdir -p src/business/game/{dto,enums,guards,interfaces} - -# ็”ŸๆˆNestJSๆจกๅ—ๆ–‡ไปถ -nest g module business/game -nest g controller business/game -nest g service business/game +``` +1. ็”จๆˆท่ฏทๆฑ‚ โ†’ LoginController (Gateway) +2. ๅ‚ๆ•ฐ้ชŒ่ฏ โ†’ DTO Validation +3. ไธšๅŠก้€ป่พ‘ โ†’ LoginService (Business) +4. ๆ ธๅฟƒๆœๅŠก โ†’ LoginCoreService (Core) +5. ๆ•ฐๆฎ่ฎฟ้—ฎ โ†’ UsersService + RedisService (Core) +6. ๆ•ฐๆฎๅญ˜ๅ‚จ โ†’ MySQL/Memory + Redis/File (Data) +7. ่ฟ”ๅ›žๅ“ๅบ” โ†’ ็”จๆˆทๆ”ถๅˆฐ็ป“ๆžœ ``` -#### 2. ๅฎž็ŽฐไธšๅŠก้€ป่พ‘ -```typescript -// src/business/game/game.module.ts -@Module({ - imports: [ - GameCoreModule, # ไพ่ต–ๆ ธๅฟƒๆœๅŠก - UsersModule, # ไพ่ต–็”จๆˆทๆ•ฐๆฎ - RedisModule, # ไพ่ต–็ผ“ๅญ˜ๆœๅŠก - ], - controllers: [GameController], - providers: [GameService], - exports: [GameService], -}) -export class GameModule {} +### WebSocketๆถˆๆฏๆต็จ‹ + +``` +1. WebSocket่ฟžๆŽฅ โ†’ LocationBroadcastGateway (Gateway) +2. ๆถˆๆฏ้ชŒ่ฏ โ†’ JWT้ชŒ่ฏ +3. ไธšๅŠกๅค„็† โ†’ LocationBroadcastService (Business) +4. ๆˆฟ้—ด็ฎก็† โ†’ ๅœฐๅ›พๅˆ†็ป„้€ป่พ‘ +5. ๆถˆๆฏๅนฟๆ’ญ โ†’ ๅŒๅœฐๅ›พ็”จๆˆท +6. ZulipๅŒๆญฅ โ†’ ZulipService (Business) ``` -#### 3. ๅˆ›ๅปบๅฏนๅบ”็š„ๆ ธๅฟƒๆœๅŠก +### ็ฎก็†ๅ‘˜ๆ“ไฝœๆต็จ‹ + +``` +1. ็ฎก็†ๅ‘˜่ฏทๆฑ‚ โ†’ AdminController (Gateway) +2. ๆƒ้™้ชŒ่ฏ โ†’ AdminGuard +3. ไธšๅŠก้€ป่พ‘ โ†’ AdminService (Business) +4. ๆ ธๅฟƒๆœๅŠก โ†’ AdminCoreService (Core) +5. ๆ•ฐๆฎๆ›ดๆ–ฐ โ†’ UsersService (Core) +6. ๅฎก่ฎกๆ—ฅๅฟ— โ†’ LoggerService (Core) +7. ่ฟ”ๅ›žๅ“ๅบ” โ†’ ็ฎก็†ๅ‘˜ๆ”ถๅˆฐ็ป“ๆžœ +``` + +--- + +## ๆ‰ฉๅฑ•ๆŒ‡ๅ— + +### ๆทปๅŠ ๆ–ฐ็š„ไธšๅŠกๆจกๅ— + +1. **ๅˆ›ๅปบ็›ฎๅฝ•็ป“ๆž„** ```bash -# ๅˆ›ๅปบๆ ธๅฟƒๆœๅŠก +mkdir -p src/gateway/game +mkdir -p src/business/game mkdir -p src/core/game_core -nest g module core/game_core -nest g service core/game_core ``` -#### 4. ๆ›ดๆ–ฐไธปๆจกๅ— +2. **ๅฎž็Žฐ็ฝ‘ๅ…ณๅฑ‚** ```typescript -// src/app.module.ts -@Module({ - imports: [ - // ... ๅ…ถไป–ๆจกๅ— - GameModule, # ๆทปๅŠ ๆ–ฐ็š„ไธšๅŠกๆจกๅ— - ], -}) -export class AppModule {} -``` - -### ๐Ÿ› ๏ธ ๆทปๅŠ ๆ–ฐ็š„ๅทฅๅ…ทๆœๅŠก - -#### 1. ๅˆ›ๅปบๅทฅๅ…ทๆœๅŠก -```bash -mkdir -p src/core/utils/notification -nest g module core/utils/notification -nest g service core/utils/notification -``` - -#### 2. ๅฎšไน‰ๆœๅŠกๆŽฅๅฃ -```typescript -// src/core/utils/notification/notification.interface.ts -export interface INotificationService { - sendPush(userId: string, message: string): Promise; - sendSMS(phone: string, message: string): Promise; -} -``` - -#### 3. ๅฎž็ŽฐๆœๅŠก -```typescript -// src/core/utils/notification/notification.service.ts -@Injectable() -export class NotificationService implements INotificationService { - async sendPush(userId: string, message: string): Promise { - // ๅฎž็ŽฐๆŽจ้€้€š็Ÿฅ้€ป่พ‘ - } - - async sendSMS(phone: string, message: string): Promise { - // ๅฎž็Žฐ็Ÿญไฟกๅ‘้€้€ป่พ‘ - } -} -``` - -#### 4. ้…็ฝฎไพ่ต–ๆณจๅ…ฅ -```typescript -// src/core/utils/notification/notification.module.ts -@Module({ - providers: [ - { - provide: 'INotificationService', - useClass: NotificationService, - }, - ], - exports: ['INotificationService'], -}) -export class NotificationModule {} -``` - -### ๐Ÿ”Œ ๆทปๅŠ ๆ–ฐ็š„APIๆŽฅๅฃ - -#### 1. ๅฎšไน‰DTO -```typescript -// src/business/game/dto/create-game.dto.ts -export class CreateGameDto { - @IsString() - @IsNotEmpty() - name: string; - - @IsString() - @IsOptional() - description?: string; -} -``` - -#### 2. ๅฎž็ŽฐController -```typescript -// src/business/game/game.controller.ts +// src/gateway/game/game.controller.ts @Controller('game') -@ApiTags('ๆธธๆˆ็ฎก็†') export class GameController { constructor(private readonly gameService: GameService) {} - + @Post() - @ApiOperation({ summary: 'ๅˆ›ๅปบๆธธๆˆ' }) - async createGame(@Body() createGameDto: CreateGameDto) { - return this.gameService.create(createGameDto); + async createGame(@Body() dto: CreateGameDto) { + return this.gameService.create(dto); } } ``` -#### 3. ๅฎž็ŽฐService +3. **ๅฎž็ŽฐไธšๅŠกๅฑ‚** ```typescript // src/business/game/game.service.ts @Injectable() @@ -689,85 +437,82 @@ export class GameService { @Inject('IGameCoreService') private readonly gameCoreService: IGameCoreService, ) {} - - async create(createGameDto: CreateGameDto) { - return this.gameCoreService.createGame(createGameDto); + + async create(dto: CreateGameDto) { + // ไธšๅŠก้€ป่พ‘ + return this.gameCoreService.createGame(dto); } } ``` -#### 4. ๆทปๅŠ ๆต‹่ฏ•็”จไพ‹ +4. **ๅฎž็Žฐๆ ธๅฟƒๅฑ‚** ```typescript -// src/business/game/game.service.spec.ts -describe('GameService', () => { - let service: GameService; - let gameCoreService: jest.Mocked; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - GameService, - { - provide: 'IGameCoreService', - useValue: { - createGame: jest.fn(), - }, - }, - ], - }).compile(); - - service = module.get(GameService); - gameCoreService = module.get('IGameCoreService'); - }); - - it('should create game', async () => { - const createGameDto = { name: 'Test Game' }; - const expectedResult = { id: 1, ...createGameDto }; - - gameCoreService.createGame.mockResolvedValue(expectedResult); - - const result = await service.create(createGameDto); - - expect(result).toEqual(expectedResult); - expect(gameCoreService.createGame).toHaveBeenCalledWith(createGameDto); - }); -}); +// src/core/game_core/game_core.service.ts +@Injectable() +export class GameCoreService { + async createGame(dto: CreateGameDto) { + // ๆ•ฐๆฎ่ฎฟ้—ฎ้€ป่พ‘ + } +} ``` -### ๐Ÿ“Š ๆ€ง่ƒฝไผ˜ๅŒ–ๅปบ่ฎฎ +5. **ๆณจๅ†Œๆจกๅ—** +```typescript +// src/app.module.ts +@Module({ + imports: [ + // ... + GameGatewayModule, + GameModule, + GameCoreModule, + ], +}) +export class AppModule {} +``` -#### 1. ็ผ“ๅญ˜็ญ–็•ฅ -- **Redis็ผ“ๅญ˜**: ็”จๆˆทไผš่ฏใ€้ชŒ่ฏ็ ใ€้ข‘็นๆŸฅ่ฏขๆ•ฐๆฎ -- **ๅ†…ๅญ˜็ผ“ๅญ˜**: ้…็ฝฎไฟกๆฏใ€้™ๆ€ๆ•ฐๆฎ -- **CDN็ผ“ๅญ˜**: ้™ๆ€่ต„ๆบๆ–‡ไปถ +### ๆ€ง่ƒฝไผ˜ๅŒ–ๅปบ่ฎฎ -#### 2. ๆ•ฐๆฎๅบ“ไผ˜ๅŒ– -- **่ฟžๆŽฅๆฑ **: ๅค็”จๆ•ฐๆฎๅบ“่ฟžๆŽฅ๏ผŒๅ‡ๅฐ‘่ฟžๆŽฅๅผ€้”€ -- **็ดขๅผ•ไผ˜ๅŒ–**: ไธบๆŸฅ่ฏขๅญ—ๆฎตๅปบ็ซ‹ๅˆ้€‚็š„็ดขๅผ• -- **ๆŸฅ่ฏขไผ˜ๅŒ–**: ้ฟๅ…N+1ๆŸฅ่ฏข๏ผŒไฝฟ็”จJOINไผ˜ๅŒ–ๅ…ณ่”ๆŸฅ่ฏข +1. **็ผ“ๅญ˜็ญ–็•ฅ** + - ็”จๆˆทไผš่ฏ โ†’ Redis + - ้ชŒ่ฏ็  โ†’ Redis๏ผˆ็ŸญๆœŸ๏ผ‰ + - ้…็ฝฎไฟกๆฏ โ†’ ๅ†…ๅญ˜็ผ“ๅญ˜ -#### 3. ๆ—ฅๅฟ—ไผ˜ๅŒ– -- **ๅผ‚ๆญฅๆ—ฅๅฟ—**: ไฝฟ็”จPino็š„ๅผ‚ๆญฅๅ†™ๅ…ฅๅŠŸ่ƒฝ -- **ๆ—ฅๅฟ—ๅˆ†็บง**: ็”Ÿไบง็Žฏๅขƒๅช่ฎฐๅฝ•ERRORๅ’ŒWARN็บงๅˆซ -- **ๆ—ฅๅฟ—่ฝฎ่ฝฌ**: ่‡ชๅŠจๆธ…็†่ฟ‡ๆœŸๆ—ฅๅฟ—ๆ–‡ไปถ +2. **ๆ•ฐๆฎๅบ“ไผ˜ๅŒ–** + - ๆทปๅŠ ็ดขๅผ• + - ไฝฟ็”จ่ฟžๆŽฅๆฑ  + - ้ฟๅ…N+1ๆŸฅ่ฏข -### ๐Ÿ”’ ๅฎ‰ๅ…จๅŠ ๅ›บๅปบ่ฎฎ +3. **ๆ—ฅๅฟ—ไผ˜ๅŒ–** + - ๅผ‚ๆญฅๅ†™ๅ…ฅ + - ๆ—ฅๅฟ—ๅˆ†็บง + - ๆ—ฅๅฟ—่ฝฎ่ฝฌ -#### 1. ๆ•ฐๆฎ้ชŒ่ฏ -- **่พ“ๅ…ฅ้ชŒ่ฏ**: ไฝฟ็”จclass-validator่ฟ›่กŒไธฅๆ ผ้ชŒ่ฏ -- **็ฑปๅž‹ๆฃ€ๆŸฅ**: TypeScript้™ๆ€็ฑปๅž‹ๆฃ€ๆŸฅ -- **SQLๆณจๅ…ฅ้˜ฒๆŠค**: TypeORMๅ‚ๆ•ฐๅŒ–ๆŸฅ่ฏข +### ๅฎ‰ๅ…จๅŠ ๅ›บๅปบ่ฎฎ -#### 2. ่ฎค่ฏๆŽˆๆƒ -- **ๅฏ†็ ๅฎ‰ๅ…จ**: bcryptๅŠ ๅฏ†๏ผŒๅผบๅฏ†็ ็ญ–็•ฅ -- **ไผš่ฏ็ฎก็†**: JWT + Redisไผš่ฏๅญ˜ๅ‚จ -- **ๆƒ้™ๆŽงๅˆถ**: ๅŸบไบŽ่ง’่‰ฒ็š„่ฎฟ้—ฎๆŽงๅˆถ(RBAC) +1. **ๆ•ฐๆฎ้ชŒ่ฏ** + - ไฝฟ็”จclass-validator + - TypeScript็ฑปๅž‹ๆฃ€ๆŸฅ + - SQLๆณจๅ…ฅ้˜ฒๆŠค -#### 3. ้€šไฟกๅฎ‰ๅ…จ -- **HTTPS**: ็”Ÿไบง็ŽฏๅขƒๅผบๅˆถHTTPS -- **CORS**: ไธฅๆ ผ็š„่ทจๅŸŸ่ฏทๆฑ‚ๆŽงๅˆถ -- **Rate Limiting**: API่ฏทๆฑ‚้ข‘็އ้™ๅˆถ +2. **่ฎค่ฏๆŽˆๆƒ** + - JWT่ฎค่ฏ + - ่ง’่‰ฒๆƒ้™ๆŽงๅˆถ + - ไผš่ฏ็ฎก็† + +3. **้€šไฟกๅฎ‰ๅ…จ** + - HTTPSๅผบๅˆถ + - CORS้…็ฝฎ + - ้ข‘็އ้™ๅˆถ --- -**๐Ÿ—๏ธ ้€š่ฟ‡ๆธ…ๆ™ฐ็š„ๆžถๆž„่ฎพ่ฎก๏ผŒWhale Town ๅฎž็Žฐไบ†้ซ˜ๅ†…่šใ€ไฝŽ่€ฆๅˆ็š„ๆจกๅ—ๅŒ–ๆžถๆž„๏ผŒๆ”ฏๆŒๅฟซ้€Ÿๅผ€ๅ‘ๅ’Œ็ตๆดป้ƒจ็ฝฒ๏ผ** \ No newline at end of file +## ๅ‚่€ƒๆ–‡ๆกฃ + +- [ๆžถๆž„้‡ๆž„ๆ–‡ๆกฃ](./ARCHITECTURE_REFACTORING.md) - ๅ››ๅฑ‚ๆžถๆž„่ฟ็งปๆŒ‡ๅ— +- [็ฝ‘ๅ…ณๅฑ‚README](../src/gateway/auth/README.md) - ็ฝ‘ๅ…ณๅฑ‚่ฏฆ็ป†่ฏดๆ˜Ž +- [ๅผ€ๅ‘่ง„่Œƒ](./development/backend_development_guide.md) - ไปฃ็ ่ง„่Œƒ +- [้ƒจ็ฝฒๆŒ‡ๅ—](./deployment/DEPLOYMENT.md) - ็”Ÿไบง็Žฏๅขƒ้ƒจ็ฝฒ + +--- + +**๐Ÿ—๏ธ ้€š่ฟ‡ๆธ…ๆ™ฐ็š„ๅ››ๅฑ‚ๆžถๆž„่ฎพ่ฎก๏ผŒWhale Town ๅฎž็Žฐไบ†่Œ่ดฃๅˆ†็ฆปใ€้ซ˜ๅ†…่šใ€ไฝŽ่€ฆๅˆ็š„็ŽฐไปฃๅŒ–ๆžถๆž„๏ผ** diff --git a/docs/development/backend_development_guide.md b/docs/development/backend_development_guide.md index 2bf08ee..035407e 100644 --- a/docs/development/backend_development_guide.md +++ b/docs/development/backend_development_guide.md @@ -1,45 +1,220 @@ # ๅŽ็ซฏๅผ€ๅ‘่ง„่ŒƒๆŒ‡ๅ— -ๆœฌๆ–‡ๆกฃๅฎšไน‰ไบ†ๅŽ็ซฏๅผ€ๅ‘็š„ๆ ธๅฟƒ่ง„่Œƒ๏ผŒๅŒ…ๆ‹ฌๆณจ้‡Š่ง„่Œƒใ€ๆ—ฅๅฟ—่ง„่Œƒใ€ไธšๅŠก้€ป่พ‘่ง„่Œƒ็ญ‰๏ผŒ็กฎไฟไปฃ็ ่ดจ้‡ๅ’Œๅ›ข้˜Ÿๅไฝœๆ•ˆ็އใ€‚ +ๆœฌๆ–‡ๆกฃๅฎšไน‰ไบ†ๅŸบไบŽๅ››ๅฑ‚ๆžถๆž„็š„ๅŽ็ซฏๅผ€ๅ‘่ง„่Œƒ๏ผŒๅŒ…ๆ‹ฌๆžถๆž„่ง„่Œƒใ€ๆณจ้‡Š่ง„่Œƒใ€ๆ—ฅๅฟ—่ง„่Œƒใ€ไปฃ็ ่ดจ้‡่ง„่Œƒ็ญ‰ใ€‚ ## ๐Ÿ“‹ ็›ฎๅฝ• +- [ๆžถๆž„่ง„่Œƒ](#ๆžถๆž„่ง„่Œƒ) - [ๆณจ้‡Š่ง„่Œƒ](#ๆณจ้‡Š่ง„่Œƒ) - [ๆ—ฅๅฟ—่ง„่Œƒ](#ๆ—ฅๅฟ—่ง„่Œƒ) -- [ไธšๅŠก้€ป่พ‘่ง„่Œƒ](#ไธšๅŠก้€ป่พ‘่ง„่Œƒ) - [ๅผ‚ๅธธๅค„็†่ง„่Œƒ](#ๅผ‚ๅธธๅค„็†่ง„่Œƒ) - [ไปฃ็ ่ดจ้‡่ง„่Œƒ](#ไปฃ็ ่ดจ้‡่ง„่Œƒ) - [ๆœ€ไฝณๅฎž่ทต](#ๆœ€ไฝณๅฎž่ทต) --- -## ๐Ÿ“ ๆณจ้‡Š่ง„่Œƒ +## ๐Ÿ—๏ธ ๆžถๆž„่ง„่Œƒ + +### ๅ››ๅฑ‚ๆžถๆž„ๅŽŸๅˆ™ + +้กน็›ฎ้‡‡็”จ **Gateway-Business-Core-Data** ๅ››ๅฑ‚ๆžถๆž„๏ผŒๆฏๅฑ‚่Œ่ดฃๆ˜Ž็กฎ๏ผš + +``` +Gateway Layer (็ฝ‘ๅ…ณๅฑ‚) + โ†“ ไพ่ต– +Business Layer (ไธšๅŠกๅฑ‚) + โ†“ ไพ่ต– +Core Layer (ๆ ธๅฟƒๅฑ‚) + โ†“ ไพ่ต– +Data Layer (ๆ•ฐๆฎๅฑ‚) +``` + +### ๅ„ๅฑ‚่Œ่ดฃ + +#### ๐ŸŒ Gateway Layer๏ผˆ็ฝ‘ๅ…ณๅฑ‚๏ผ‰ + +**ไฝ็ฝฎ๏ผš** `src/gateway/` + +**่Œ่ดฃ๏ผš** +- HTTP/WebSocketๅ่ฎฎๅค„็† +- ่ฏทๆฑ‚ๅ‚ๆ•ฐ้ชŒ่ฏ๏ผˆDTO๏ผ‰ +- ่ทฏ็”ฑ็ฎก็† +- ่ฎค่ฏๅฎˆๅซ +- ้”™่ฏฏ่ฝฌๆข + +**่ง„่Œƒ๏ผš** +```typescript +// โœ… ๆญฃ็กฎ๏ผšๅชๅšๅ่ฎฎ่ฝฌๆข +@Controller('auth') +export class LoginController { + constructor(private readonly loginService: LoginService) {} + + @Post('login') + async login(@Body() dto: LoginDto, @Res() res: Response) { + const result = await this.loginService.login(dto); + this.handleResponse(result, res); + } +} + +// โŒ ้”™่ฏฏ๏ผšๅŒ…ๅซไธšๅŠก้€ป่พ‘ +@Controller('auth') +export class LoginController { + @Post('login') + async login(@Body() dto: LoginDto) { + const user = await this.usersService.findByEmail(dto.email); + const isValid = await bcrypt.compare(dto.password, user.password); + // ... ๆ›ดๅคšไธšๅŠก้€ป่พ‘ + } +} +``` + +#### ๐ŸŽฏ Business Layer๏ผˆไธšๅŠกๅฑ‚๏ผ‰ + +**ไฝ็ฝฎ๏ผš** `src/business/` + +**่Œ่ดฃ๏ผš** +- ไธšๅŠก้€ป่พ‘ๅฎž็Žฐ +- ๆœๅŠกๅ่ฐƒ +- ไธšๅŠก่ง„ๅˆ™้ชŒ่ฏ +- ไบ‹ๅŠก็ฎก็† + +**่ง„่Œƒ๏ผš** +```typescript +// โœ… ๆญฃ็กฎ๏ผšๅฎž็ŽฐไธšๅŠก้€ป่พ‘ +@Injectable() +export class LoginService { + constructor( + private readonly loginCoreService: LoginCoreService, + private readonly emailService: EmailService, + ) {} + + async login(dto: LoginDto): Promise> { + try { + // 1. ่ฐƒ็”จๆ ธๅฟƒๆœๅŠก้ชŒ่ฏ + const user = await this.loginCoreService.validateUser(dto); + + // 2. ไธšๅŠก้€ป่พ‘๏ผš็”ŸๆˆToken + const tokens = await this.loginCoreService.generateTokens(user); + + // 3. ไธšๅŠก้€ป่พ‘๏ผšๅ‘้€็™ปๅฝ•้€š็Ÿฅ + await this.emailService.sendLoginNotification(user.email); + + return { success: true, data: tokens }; + } catch (error) { + return { success: false, message: error.message }; + } + } +} + +// โŒ ้”™่ฏฏ๏ผš็›ดๆŽฅ่ฎฟ้—ฎๆ•ฐๆฎๅบ“ +@Injectable() +export class LoginService { + async login(dto: LoginDto) { + const user = await this.userRepository.findOne({ email: dto.email }); + // ... + } +} +``` + +#### โš™๏ธ Core Layer๏ผˆๆ ธๅฟƒๅฑ‚๏ผ‰ + +**ไฝ็ฝฎ๏ผš** `src/core/` + +**่Œ่ดฃ๏ผš** +- ๆ•ฐๆฎ่ฎฟ้—ฎ +- ๅŸบ็ก€่ฎพๆ–ฝ +- ๅค–้ƒจ็ณป็ปŸ้›†ๆˆ +- ๅทฅๅ…ทๆœๅŠก + +**่ง„่Œƒ๏ผš** +```typescript +// โœ… ๆญฃ็กฎ๏ผšๆไพ›ๆŠ€ๆœฏๅŸบ็ก€่ฎพๆ–ฝ +@Injectable() +export class LoginCoreService { + constructor( + @Inject('IUsersService') + private readonly usersService: IUsersService, + @Inject('IRedisService') + private readonly redisService: IRedisService, + ) {} + + async validateUser(dto: LoginDto): Promise { + const user = await this.usersService.findByEmail(dto.email); + if (!user) { + throw new UnauthorizedException('็”จๆˆทไธๅญ˜ๅœจ'); + } + + const isValid = await bcrypt.compare(dto.password, user.password); + if (!isValid) { + throw new UnauthorizedException('ๅฏ†็ ้”™่ฏฏ'); + } + + return user; + } +} + +// โŒ ้”™่ฏฏ๏ผšๅŒ…ๅซไธšๅŠก้€ป่พ‘ +@Injectable() +export class LoginCoreService { + async validateUser(dto: LoginDto) { + // ๅ‘้€้‚ฎไปถ้€š็Ÿฅ - ่ฟ™ๆ˜ฏไธšๅŠก้€ป่พ‘๏ผŒๅบ”่ฏฅๅœจBusinessๅฑ‚ + await this.emailService.sendLoginNotification(user.email); + } +} +``` + +### ๆจกๅ—็ป„็ป‡่ง„่Œƒ + +```typescript +// ๆจกๅ—ๅ‘ฝๅ๏ผšๅŠŸ่ƒฝๅ.module.ts +// ๆœๅŠกๅ‘ฝๅ๏ผšๅŠŸ่ƒฝๅ.service.ts +// ๆŽงๅˆถๅ™จๅ‘ฝๅ๏ผšๅŠŸ่ƒฝๅ.controller.ts +// ็ฝ‘ๅ…ณๅ‘ฝๅ๏ผšๅŠŸ่ƒฝๅ.gateway.ts + +// โœ… ๆญฃ็กฎ็š„ๆจกๅ—็ป“ๆž„ +src/ +โ”œโ”€โ”€ gateway/ +โ”‚ โ””โ”€โ”€ auth/ +โ”‚ โ”œโ”€โ”€ login.controller.ts +โ”‚ โ”œโ”€โ”€ register.controller.ts +โ”‚ โ”œโ”€โ”€ jwt_auth.guard.ts +โ”‚ โ”œโ”€โ”€ dto/ +โ”‚ โ””โ”€โ”€ auth.gateway.module.ts +โ”œโ”€โ”€ business/ +โ”‚ โ””โ”€โ”€ auth/ +โ”‚ โ”œโ”€โ”€ login.service.ts +โ”‚ โ”œโ”€โ”€ register.service.ts +โ”‚ โ””โ”€โ”€ auth.module.ts +โ””โ”€โ”€ core/ + โ””โ”€โ”€ login_core/ + โ”œโ”€โ”€ login_core.service.ts + โ””โ”€โ”€ login_core.module.ts +``` + +--- + +## ๏ฟฝ ๆณจ้‡Š่ง„่ง„่Œƒ ### ๆ–‡ไปถๅคดๆณจ้‡Š -ๆฏไธช TypeScript ๆ–‡ไปถ้ƒฝๅฟ…้กปๅŒ…ๅซๅฎŒๆ•ด็š„ๆ–‡ไปถๅคดๆณจ้‡Š๏ผš - ```typescript /** - * ๆ–‡ไปถๅŠŸ่ƒฝๆ่ฟฐ + * ็”จๆˆท็™ปๅฝ•ๆœๅŠก * * ๅŠŸ่ƒฝๆ่ฟฐ๏ผš - * - ไธป่ฆๅŠŸ่ƒฝ็‚น1 - * - ไธป่ฆๅŠŸ่ƒฝ็‚น2 - * - ไธป่ฆๅŠŸ่ƒฝ็‚น3 + * - ๅค„็†็”จๆˆท็™ปๅฝ•ไธšๅŠก้€ป่พ‘ + * - ๅ่ฐƒ็™ปๅฝ•ๆ ธๅฟƒๆœๅŠกๅ’Œ้‚ฎไปถๆœๅŠก + * - ็”ŸๆˆJWTไปค็‰Œ * - * ่Œ่ดฃๅˆ†็ฆป๏ผš - * - ่Œ่ดฃๆ่ฟฐ1 - * - ่Œ่ดฃๆ่ฟฐ2 + * ๆžถๆž„ๅฑ‚็บง๏ผšBusiness Layer * - * ๆœ€่ฟ‘ไฟฎๆ”น๏ผš - * - YYYY-MM-DD: ไฟฎๆ”น็ฑปๅž‹ - ๅ…ทไฝ“ไฟฎๆ”นๅ†…ๅฎนๆ่ฟฐ - * - YYYY-MM-DD: ไฟฎๆ”น็ฑปๅž‹ - ๅ…ทไฝ“ไฟฎๆ”นๅ†…ๅฎนๆ่ฟฐ + * ไพ่ต–ๆœๅŠก๏ผš + * - LoginCoreService: ็™ปๅฝ•ๆ ธๅฟƒ้€ป่พ‘ + * - EmailService: ้‚ฎไปถๅ‘้€ๆœๅŠก * * @author ไฝœ่€…ๅ - * @version x.x.x - * @since ๅˆ›ๅปบๆ—ฅๆœŸ - * @lastModified ๆœ€ๅŽไฟฎๆ”นๆ—ฅๆœŸ + * @version 1.0.0 + * @since 2025-01-01 */ ``` @@ -47,149 +222,75 @@ ```typescript /** - * ็ฑปๅŠŸ่ƒฝๆ่ฟฐ + * ็™ปๅฝ•ไธšๅŠกๆœๅŠก * * ่Œ่ดฃ๏ผš - * - ไธป่ฆ่Œ่ดฃ1 - * - ไธป่ฆ่Œ่ดฃ2 + * - ๅฎž็Žฐ็”จๆˆท็™ปๅฝ•ไธšๅŠก้€ป่พ‘ + * - ๅ่ฐƒๆ ธๅฟƒๆœๅŠกๅฎŒๆˆ็™ปๅฝ•ๆต็จ‹ + * - ๅค„็†็™ปๅฝ•็›ธๅ…ณ็š„ไธšๅŠก่ง„ๅˆ™ * * ไธป่ฆๆ–นๆณ•๏ผš - * - method1() - ๆ–นๆณ•1ๅŠŸ่ƒฝ - * - method2() - ๆ–นๆณ•2ๅŠŸ่ƒฝ - * - * ไฝฟ็”จๅœบๆ™ฏ๏ผš - * - ๅœบๆ™ฏๆ่ฟฐ + * - login() - ็”จๆˆท็™ปๅฝ• + * - verificationCodeLogin() - ้ชŒ่ฏ็ ็™ปๅฝ• + * - refreshToken() - ๅˆทๆ–ฐไปค็‰Œ */ @Injectable() -export class ExampleService { - // ็ฑปๅฎž็Žฐ +export class LoginService { + // ๅฎž็Žฐ } ``` -### ๆ–นๆณ•ๆณจ้‡Š๏ผˆไธ‰็บงๆณจ้‡Šๆ ‡ๅ‡†๏ผ‰ +### ๆ–นๆณ•ๆณจ้‡Š๏ผˆไธ‰็บงๆ ‡ๅ‡†๏ผ‰ -**ๅฟ…้กปๅŒ…ๅซไปฅไธ‹ไธ‰ไธช็บงๅˆซ็š„ๆณจ้‡Š๏ผš** - -#### 1. ๅŠŸ่ƒฝๆ่ฟฐ็บงๅˆซ ```typescript /** - * ็”จๆˆท็™ปๅฝ•้ชŒ่ฏ - */ -``` - -#### 2. ไธšๅŠก้€ป่พ‘็บงๅˆซ -```typescript -/** - * ็”จๆˆท็™ปๅฝ•้ชŒ่ฏ + * ็”จๆˆท็™ปๅฝ• * * ไธšๅŠก้€ป่พ‘๏ผš - * 1. ้ชŒ่ฏ็”จๆˆทๅๆˆ–้‚ฎ็ฎฑๆ ผๅผ - * 2. ๆŸฅๆ‰พ็”จๆˆท่ฎฐๅฝ• - * 3. ้ชŒ่ฏๅฏ†็ ๅ“ˆๅธŒๅ€ผ - * 4. ๆฃ€ๆŸฅ็”จๆˆท็Šถๆ€ๆ˜ฏๅฆๅ…่ฎธ็™ปๅฝ• - * 5. ่ฎฐๅฝ•็™ปๅฝ•ๆ—ฅๅฟ— - * 6. ่ฟ”ๅ›ž่ฎค่ฏ็ป“ๆžœ - */ -``` - -#### 3. ๆŠ€ๆœฏๅฎž็Žฐ็บงๅˆซ -```typescript -/** - * ็”จๆˆท็™ปๅฝ•้ชŒ่ฏ + * 1. ่ฐƒ็”จๆ ธๅฟƒๆœๅŠก้ชŒ่ฏ็”จๆˆทๅ‡ญ่ฏ + * 2. ็”Ÿๆˆ่ฎฟ้—ฎไปค็‰Œๅ’Œๅˆทๆ–ฐไปค็‰Œ + * 3. ๅ‘้€็™ปๅฝ•ๆˆๅŠŸ้€š็Ÿฅ้‚ฎไปถ + * 4. ่ฎฐๅฝ•็™ปๅฝ•ๆ—ฅๅฟ— + * 5. ่ฟ”ๅ›ž็™ปๅฝ•็ป“ๆžœ * - * ไธšๅŠก้€ป่พ‘๏ผš - * 1. ้ชŒ่ฏ็”จๆˆทๅๆˆ–้‚ฎ็ฎฑๆ ผๅผ - * 2. ๆŸฅๆ‰พ็”จๆˆท่ฎฐๅฝ• - * 3. ้ชŒ่ฏๅฏ†็ ๅ“ˆๅธŒๅ€ผ - * 4. ๆฃ€ๆŸฅ็”จๆˆท็Šถๆ€ๆ˜ฏๅฆๅ…่ฎธ็™ปๅฝ• - * 5. ่ฎฐๅฝ•็™ปๅฝ•ๆ—ฅๅฟ— - * 6. ่ฟ”ๅ›ž่ฎค่ฏ็ป“ๆžœ - * - * @param loginRequest ็™ปๅฝ•่ฏทๆฑ‚ๆ•ฐๆฎ - * @returns ่ฎค่ฏ็ป“ๆžœ๏ผŒๅŒ…ๅซ็”จๆˆทไฟกๆฏๅ’Œ่ฎค่ฏ็Šถๆ€ - * @throws UnauthorizedException ็”จๆˆทๅๆˆ–ๅฏ†็ ้”™่ฏฏๆ—ถ - * @throws ForbiddenException ็”จๆˆท็Šถๆ€ไธๅ…่ฎธ็™ปๅฝ•ๆ—ถ + * @param dto ็™ปๅฝ•่ฏทๆฑ‚ๆ•ฐๆฎ + * @returns ็™ปๅฝ•็ป“ๆžœ๏ผŒๅŒ…ๅซ็”จๆˆทไฟกๆฏๅ’Œไปค็‰Œ + * @throws UnauthorizedException ็”จๆˆทๅๆˆ–ๅฏ†็ ้”™่ฏฏ + * @throws ForbiddenException ็”จๆˆท็Šถๆ€ไธๅ…่ฎธ็™ปๅฝ• * * @example * ```typescript - * const result = await loginService.validateUser({ + * const result = await loginService.login({ * identifier: 'user@example.com', * password: 'password123' * }); * ``` */ -async validateUser(loginRequest: LoginRequest): Promise { - // ๅฎž็Žฐไปฃ็  +async login(dto: LoginDto): Promise> { + // ๅฎž็Žฐ } ``` ### ไฟฎๆ”น่ฎฐๅฝ•่ง„่Œƒ -#### ไฟฎๆ”น็ฑปๅž‹ๅฎšไน‰ - -- **ไปฃ็ ่ง„่Œƒไผ˜ๅŒ–** - ๅ‘ฝๅ่ง„่Œƒใ€ๆณจ้‡Š่ง„่Œƒใ€ไปฃ็ ๆธ…็†็ญ‰ -- **ๅŠŸ่ƒฝๆ–ฐๅขž** - ๆทปๅŠ ๆ–ฐ็š„ๅŠŸ่ƒฝๆˆ–ๆ–นๆณ• -- **ๅŠŸ่ƒฝไฟฎๆ”น** - ไฟฎๆ”น็Žฐๆœ‰ๅŠŸ่ƒฝ็š„ๅฎž็Žฐ -- **Bugไฟฎๅค** - ไฟฎๅคไปฃ็ ็ผบ้™ท -- **ๆ€ง่ƒฝไผ˜ๅŒ–** - ๆๅ‡ไปฃ็ ๆ€ง่ƒฝ -- **้‡ๆž„** - ไปฃ็ ็ป“ๆž„่ฐƒๆ•ดไฝ†ๅŠŸ่ƒฝไธๅ˜ - -#### ไฟฎๆ”น่ฎฐๅฝ•ๆ ผๅผ - ```typescript /** * ๆœ€่ฟ‘ไฟฎๆ”น๏ผš - * - 2025-01-07: ไปฃ็ ่ง„่Œƒไผ˜ๅŒ– - ๆธ…็†ๆœชไฝฟ็”จ็š„ๅฏผๅ…ฅ(EmailSendResult, crypto) - * - 2025-01-07: ไปฃ็ ่ง„่Œƒไผ˜ๅŒ– - ไฟฎๅคๅธธ้‡ๅ‘ฝๅ(saltRounds -> SALT_ROUNDS) - * - 2025-01-07: ๅŠŸ่ƒฝๆ–ฐๅขž - ๆทปๅŠ ็”จๆˆท้ชŒ่ฏ็ ็™ปๅฝ•ๅŠŸ่ƒฝ - * - 2025-01-06: Bugไฟฎๅค - ไฟฎๅค้‚ฎ็ฎฑ้ชŒ่ฏ้€ป่พ‘้”™่ฏฏ + * - 2025-01-15: ๆžถๆž„้‡ๆž„ - ่ฟ็งปๅˆฐๅ››ๅฑ‚ๆžถๆž„๏ผŒๅˆ†็ฆป็ฝ‘ๅ…ณๅฑ‚ๅ’ŒไธšๅŠกๅฑ‚ + * - 2025-01-10: ๅŠŸ่ƒฝๆ–ฐๅขž - ๆทปๅŠ ้ชŒ่ฏ็ ็™ปๅฝ•ๅŠŸ่ƒฝ + * - 2025-01-08: Bugไฟฎๅค - ไฟฎๅคTokenๅˆทๆ–ฐ้€ป่พ‘้”™่ฏฏ + * - 2025-01-05: ไปฃ็ ่ง„่Œƒไผ˜ๅŒ– - ็ปŸไธ€ๅผ‚ๅธธๅค„็†ๆ ผๅผ + * - 2025-01-03: ๆ€ง่ƒฝไผ˜ๅŒ– - ไผ˜ๅŒ–ๆ•ฐๆฎๅบ“ๆŸฅ่ฏขๆ€ง่ƒฝ * - * @version 1.0.1 (ไฟฎๆ”นๅŽ้œ€่ฆ้€’ๅขž็‰ˆๆœฌๅท) - * @lastModified 2025-01-07 + * @version 2.0.0 + * @lastModified 2025-01-15 */ ``` -#### ไฟฎๆ”น่ฎฐๅฝ•้•ฟๅบฆ้™ๅˆถ - -**้‡่ฆ๏ผšไธบไฟๆŒๆ–‡ไปถๅคดๆณจ้‡Š็ฎ€ๆด๏ผŒไฟฎๆ”น่ฎฐๅฝ•ๅชไฟ็•™ๆœ€่ฟ‘็š„5ๆฌกไฟฎๆ”นใ€‚** - -- โœ… **ไฟ็•™ๆœ€ๆ–ฐ5ๆก่ฎฐๅฝ•** - ไพฟไบŽๅฟซ้€Ÿไบ†่งฃๆœ€่ฟ‘ๅ˜ๆ›ด -- โœ… **่ถ…ๅ‡บๆ—ถๅˆ ้™คๆœ€ๆ—ง่ฎฐๅฝ•** - ไฟๆŒๆณจ้‡Š็ฎ€ๆด -- โœ… **้‡่ฆไฟฎๆ”นๅฏๆ ‡ๆณจ** - ้‡ๅคง็‰ˆๆœฌๆ›ดๆ–ฐๅฏ็‰นๅˆซๆ ‡ๆณจ - -```typescript -// โœ… ๆญฃ็กฎ็คบไพ‹๏ผšไฟๆŒๆœ€ๆ–ฐ5ๆก่ฎฐๅฝ• -/** - * ๆœ€่ฟ‘ไฟฎๆ”น๏ผš - * - 2025-01-07: ๅŠŸ่ƒฝๆ–ฐๅขž - ๆทปๅŠ ็”จๆˆทๅคดๅƒไธŠไผ ๅŠŸ่ƒฝ - * - 2025-01-06: Bugไฟฎๅค - ไฟฎๅค้‚ฎ็ฎฑ้ชŒ่ฏ้€ป่พ‘้”™่ฏฏ - * - 2025-01-05: ไปฃ็ ่ง„่Œƒไผ˜ๅŒ– - ็ปŸไธ€ๅผ‚ๅธธๅค„็†ๆ ผๅผ - * - 2025-01-04: ๅŠŸ่ƒฝไฟฎๆ”น - ไผ˜ๅŒ–็”จๆˆท็Šถๆ€็ฎก็†้€ป่พ‘ - * - 2025-01-03: ๆ€ง่ƒฝไผ˜ๅŒ– - ไผ˜ๅŒ–ๆ•ฐๆฎๅบ“ๆŸฅ่ฏขๆ€ง่ƒฝ - * - * @version 1.3.0 - */ - -// โŒ ้”™่ฏฏ็คบไพ‹๏ผš่ฎฐๅฝ•่ฟ‡ๅคš๏ผŒๆณจ้‡Šๅ†—้•ฟ -/** - * ๆœ€่ฟ‘ไฟฎๆ”น๏ผš - * - 2025-01-07: ๅŠŸ่ƒฝๆ–ฐๅขž - ๆทปๅŠ ็”จๆˆทๅคดๅƒไธŠไผ ๅŠŸ่ƒฝ - * - 2025-01-06: Bugไฟฎๅค - ไฟฎๅค้‚ฎ็ฎฑ้ชŒ่ฏ้€ป่พ‘้”™่ฏฏ - * - 2025-01-05: ไปฃ็ ่ง„่Œƒไผ˜ๅŒ– - ็ปŸไธ€ๅผ‚ๅธธๅค„็†ๆ ผๅผ - * - 2025-01-04: ๅŠŸ่ƒฝไฟฎๆ”น - ไผ˜ๅŒ–็”จๆˆท็Šถๆ€็ฎก็†้€ป่พ‘ - * - 2025-01-03: ๆ€ง่ƒฝไผ˜ๅŒ– - ไผ˜ๅŒ–ๆ•ฐๆฎๅบ“ๆŸฅ่ฏขๆ€ง่ƒฝ - * - 2025-01-02: ้‡ๆž„ - ้‡ๆž„็”จๆˆท่ฎค่ฏ้€ป่พ‘ - * - 2025-01-01: ๅŠŸ่ƒฝๆ–ฐๅขž - ๆทปๅŠ ็”จๆˆทๆƒ้™็ฎก็† - * - 2024-12-31: Bugไฟฎๅค - ไฟฎๅค็™ปๅฝ•่ถ…ๆ—ถ้—ฎ้ข˜ - * // ... ๆ›ดๅคš่ฎฐๅฝ•ๅฏผ่‡ดๆณจ้‡Š่ฟ‡้•ฟ - */ -``` - -#### ็‰ˆๆœฌๅท้€’ๅขž่ง„ๅˆ™ - -- **ไปฃ็ ่ง„่Œƒไผ˜ๅŒ–ใ€Bugไฟฎๅค** โ†’ ไฟฎ่ฎข็‰ˆๆœฌ +1 (1.0.0 โ†’ 1.0.1) -- **ๅŠŸ่ƒฝๆ–ฐๅขžใ€ๅŠŸ่ƒฝไฟฎๆ”น** โ†’ ๆฌก็‰ˆๆœฌ +1 (1.0.1 โ†’ 1.1.0) -- **้‡ๆž„ใ€ๆžถๆž„ๅ˜ๆ›ด** โ†’ ไธป็‰ˆๆœฌ +1 (1.1.0 โ†’ 2.0.0) +**ไฟฎๆ”น่ฎฐๅฝ•ๅŽŸๅˆ™๏ผš** +- ๅชไฟ็•™ๆœ€่ฟ‘5ๆฌกไฟฎๆ”น +- ๅŒ…ๅซๆ—ฅๆœŸใ€็ฑปๅž‹ใ€ๆ่ฟฐ +- ้‡ๅคง็‰ˆๆœฌๆ›ดๆ–ฐๆ ‡ๆณจ็‰ˆๆœฌๅท --- @@ -199,22 +300,37 @@ async validateUser(loginRequest: LoginRequest): Promise { ```typescript // ERROR - ็ณป็ปŸ้”™่ฏฏ๏ผŒ้œ€่ฆ็ซ‹ๅณๅค„็† -this.logger.error('็”จๆˆท็™ปๅฝ•ๅคฑ่ดฅ', { userId, error: error.message }); +this.logger.error('็”จๆˆท็™ปๅฝ•ๅคฑ่ดฅ', { + userId, + error: error.message, + stack: error.stack +}); -// WARN - ่ญฆๅ‘Šไฟกๆฏ๏ผŒ้œ€่ฆๅ…ณๆณจไฝ†ไธๅฝฑๅ“็ณป็ปŸ่ฟ่กŒ -this.logger.warn('็”จๆˆทๅคšๆฌก็™ปๅฝ•ๅคฑ่ดฅ', { userId, attemptCount }); +// WARN - ่ญฆๅ‘Šไฟกๆฏ๏ผŒ้œ€่ฆๅ…ณๆณจ +this.logger.warn('็”จๆˆทๅคšๆฌก็™ปๅฝ•ๅคฑ่ดฅ', { + userId, + attemptCount, + ip: request.ip +}); -// INFO - ้‡่ฆ็š„ไธšๅŠกๆ“ไฝœ่ฎฐๅฝ• -this.logger.info('็”จๆˆท็™ปๅฝ•ๆˆๅŠŸ', { userId, loginTime: new Date() }); +// INFO - ้‡่ฆ็š„ไธšๅŠกๆ“ไฝœ +this.logger.info('็”จๆˆท็™ปๅฝ•ๆˆๅŠŸ', { + userId, + loginTime: new Date(), + ip: request.ip +}); -// DEBUG - ่ฐƒ่ฏ•ไฟกๆฏ๏ผŒไป…ๅœจๅผ€ๅ‘็Žฏๅขƒไฝฟ็”จ -this.logger.debug('้ชŒ่ฏ็”จๆˆทๅฏ†็ ', { userId, hashedPassword: '***' }); +// DEBUG - ่ฐƒ่ฏ•ไฟกๆฏ๏ผˆไป…ๅผ€ๅ‘็Žฏๅขƒ๏ผ‰ +this.logger.debug('้ชŒ่ฏ็”จๆˆทๅฏ†็ ', { + userId, + passwordHash: '***' +}); ``` ### ๆ—ฅๅฟ—ๆ ผๅผ่ง„่Œƒ ```typescript -// โœ… ๆญฃ็กฎๆ ผๅผ +// โœ… ๆญฃ็กฎ๏ผš็ป“ๆž„ๅŒ–ๆ—ฅๅฟ— this.logger.info('ๆ“ไฝœๆ่ฟฐ', { userId: 'user123', action: 'login', @@ -222,68 +338,26 @@ this.logger.info('ๆ“ไฝœๆ่ฟฐ', { metadata: { ip: '192.168.1.1' } }); -// โŒ ้”™่ฏฏๆ ผๅผ -this.logger.info('็”จๆˆท็™ปๅฝ•'); +// โŒ ้”™่ฏฏ๏ผšๅญ—็ฌฆไธฒๆ‹ผๆŽฅ this.logger.info(`็”จๆˆท${userId}็™ปๅฝ•ๆˆๅŠŸ`); ``` ---- - -## ๐Ÿ—๏ธ ไธšๅŠก้€ป่พ‘่ง„่Œƒ - -### ้˜ฒๅพกๆ€ง็ผ–็จ‹ +### ๆ•ๆ„Ÿไฟกๆฏๅค„็† ```typescript -async getUserById(userId: string): Promise { - // 1. ๅ‚ๆ•ฐ้ชŒ่ฏ - if (!userId) { - throw new BadRequestException('็”จๆˆทIDไธ่ƒฝไธบ็ฉบ'); - } +// โœ… ๆญฃ็กฎ๏ผš้š่—ๆ•ๆ„Ÿไฟกๆฏ +this.logger.info('็”จๆˆทๆณจๅ†Œ', { + email: user.email, + password: '***', // ๅฏ†็ ไธ่ฎฐๅฝ• + apiKey: '***' // APIๅฏ†้’ฅไธ่ฎฐๅฝ• +}); - // 2. ไธšๅŠก้€ป่พ‘้ชŒ่ฏ - const user = await this.usersService.findOne(userId); - if (!user) { - throw new NotFoundException('็”จๆˆทไธๅญ˜ๅœจ'); - } - - // 3. ็Šถๆ€ๆฃ€ๆŸฅ - if (user.status === UserStatus.DELETED) { - throw new ForbiddenException('็”จๆˆทๅทฒ่ขซๅˆ ้™ค'); - } - - // 4. ่ฟ”ๅ›ž็ป“ๆžœ - return user; -} -``` - -### ไธšๅŠก้€ป่พ‘ๅˆ†ๅฑ‚ - -```typescript -// Controller ๅฑ‚ - ๅชๅค„็†HTTP่ฏทๆฑ‚ๅ’Œๅ“ๅบ” -@Controller('users') -export class UsersController { - @Get(':id') - async getUser(@Param('id') id: string) { - return this.usersService.getUserById(id); - } -} - -// Service ๅฑ‚ - ๅค„็†ไธšๅŠก้€ป่พ‘ -@Injectable() -export class UsersService { - async getUserById(id: string): Promise { - // ไธšๅŠก้€ป่พ‘ๅฎž็Žฐ - return this.usersCoreService.findUserById(id); - } -} - -// Core ๅฑ‚ - ๆ ธๅฟƒไธšๅŠกๅฎž็Žฐ -@Injectable() -export class UsersCoreService { - async findUserById(id: string): Promise { - // ๆ ธๅฟƒ้€ป่พ‘ๅฎž็Žฐ - } -} +// โŒ ้”™่ฏฏ๏ผšๆšด้œฒๆ•ๆ„Ÿไฟกๆฏ +this.logger.info('็”จๆˆทๆณจๅ†Œ', { + email: user.email, + password: user.password, // ๅฑ้™ฉ๏ผ + apiKey: user.apiKey // ๅฑ้™ฉ๏ผ +}); ``` --- @@ -312,38 +386,66 @@ throw new ConflictException('็”จๆˆทๅๅทฒๅญ˜ๅœจ'); throw new InternalServerErrorException('็ณป็ปŸๅ†…้ƒจ้”™่ฏฏ'); ``` -### ๅผ‚ๅธธๅค„็†ๆจกๅผ +### ๅˆ†ๅฑ‚ๅผ‚ๅธธๅค„็† ```typescript -async createUser(userData: CreateUserDto): Promise { - try { - // 1. ๅ‚ๆ•ฐ้ชŒ่ฏ - this.validateUserData(userData); +// Gateway Layer - ่ฝฌๆขไธบHTTPๅ“ๅบ” +@Controller('auth') +export class LoginController { + @Post('login') + async login(@Body() dto: LoginDto, @Res() res: Response) { + const result = await this.loginService.login(dto); - // 2. ไธšๅŠก้€ป่พ‘ๆฃ€ๆŸฅ - await this.checkUserExists(userData.email); + if (result.success) { + res.status(HttpStatus.OK).json(result); + } else { + const statusCode = this.getErrorStatusCode(result); + res.status(statusCode).json(result); + } + } +} + +// Business Layer - ่ฟ”ๅ›žไธšๅŠกๅ“ๅบ” +@Injectable() +export class LoginService { + async login(dto: LoginDto): Promise> { + try { + const user = await this.loginCoreService.validateUser(dto); + const tokens = await this.loginCoreService.generateTokens(user); + + return { + success: true, + data: tokens, + message: '็™ปๅฝ•ๆˆๅŠŸ' + }; + } catch (error) { + this.logger.error('็™ปๅฝ•ๅคฑ่ดฅ', { dto, error: error.message }); + + return { + success: false, + message: error.message, + error_code: 'LOGIN_FAILED' + }; + } + } +} + +// Core Layer - ๆŠ›ๅ‡บๆŠ€ๆœฏๅผ‚ๅธธ +@Injectable() +export class LoginCoreService { + async validateUser(dto: LoginDto): Promise { + const user = await this.usersService.findByEmail(dto.email); - // 3. ๆ‰ง่กŒๅˆ›ๅปบๆ“ไฝœ - const user = await this.usersRepository.create(userData); - - // 4. ่ฎฐๅฝ•ๆˆๅŠŸๆ—ฅๅฟ— - this.logger.info('็”จๆˆทๅˆ›ๅปบๆˆๅŠŸ', { userId: user.id }); - - return user; - } catch (error) { - // 5. ่ฎฐๅฝ•้”™่ฏฏๆ—ฅๅฟ— - this.logger.error('็”จๆˆทๅˆ›ๅปบๅคฑ่ดฅ', { - userData: { ...userData, password: '***' }, - error: error.message - }); - - // 6. ้‡ๆ–ฐๆŠ›ๅ‡บไธšๅŠกๅผ‚ๅธธ - if (error instanceof BadRequestException) { - throw error; + if (!user) { + throw new UnauthorizedException('็”จๆˆทไธๅญ˜ๅœจ'); } - // 7. ่ฝฌๆขไธบ็ณป็ปŸๅผ‚ๅธธ - throw new InternalServerErrorException('็”จๆˆทๅˆ›ๅปบๅคฑ่ดฅ'); + const isValid = await bcrypt.compare(dto.password, user.password); + if (!isValid) { + throw new UnauthorizedException('ๅฏ†็ ้”™่ฏฏ'); + } + + return user; } } ``` @@ -354,152 +456,233 @@ async createUser(userData: CreateUserDto): Promise { ### ไปฃ็ ๆฃ€ๆŸฅๆธ…ๅ• -ๅœจๆไบคไปฃ็ ๅ‰๏ผŒ่ฏท็กฎไฟ๏ผš +ๆไบคไปฃ็ ๅ‰็กฎไฟ๏ผš + +- [ ] **ๆžถๆž„่ง„่Œƒ** + - [ ] ไปฃ็ ๆ”พๅœจๆญฃ็กฎ็š„ๆžถๆž„ๅฑ‚ + - [ ] ๆฒกๆœ‰่ทจๅฑ‚็›ดๆŽฅ่ฐƒ็”จ๏ผˆๅฆ‚Gateway็›ดๆŽฅ่ฐƒ็”จCore๏ผ‰ + - [ ] ไพ่ต–ๆ–นๅ‘ๆญฃ็กฎ๏ผˆไธŠๅฑ‚ไพ่ต–ไธ‹ๅฑ‚๏ผ‰ + - [ ] ๆจกๅ—่Œ่ดฃๅ•ไธ€ๆ˜Ž็กฎ - [ ] **ๆณจ้‡ŠๅฎŒๆ•ดๆ€ง** - - [ ] ๆ–‡ไปถๅคดๆณจ้‡ŠๅŒ…ๅซๅŠŸ่ƒฝๆ่ฟฐใ€ไฟฎๆ”น่ฎฐๅฝ•ใ€ไฝœ่€…ไฟกๆฏ - - [ ] ็ฑปๆณจ้‡ŠๅŒ…ๅซ่Œ่ดฃใ€ไธป่ฆๆ–นๆณ•ใ€ไฝฟ็”จๅœบๆ™ฏ - - [ ] ๆ–นๆณ•ๆณจ้‡ŠๅŒ…ๅซไธ‰็บงๆณจ้‡Š๏ผˆๅŠŸ่ƒฝใ€ไธšๅŠก้€ป่พ‘ใ€ๆŠ€ๆœฏๅฎž็Žฐ๏ผ‰ - - [ ] ไฟฎๆ”น็Žฐๆœ‰ๆ–‡ไปถๆ—ถๆทปๅŠ ไบ†ไฟฎๆ”น่ฎฐๅฝ•ๅ’Œๆ›ดๆ–ฐ็‰ˆๆœฌๅท - - [ ] ไฟฎๆ”น่ฎฐๅฝ•ๅชไฟ็•™ๆœ€่ฟ‘5ๆฌก๏ผŒไฟๆŒๆณจ้‡Š็ฎ€ๆด - -- [ ] **ไธšๅŠก้€ป่พ‘ๅฎŒๆ•ดๆ€ง** - - [ ] ๆ‰€ๆœ‰ๅ‚ๆ•ฐ้ƒฝ่ฟ›่กŒไบ†้ชŒ่ฏ - - [ ] ๆ‰€ๆœ‰ๅผ‚ๅธธๆƒ…ๅ†ต้ƒฝ่ฟ›่กŒไบ†ๅค„็† - - [ ] ๅ…ณ้”ฎๆ“ไฝœ้ƒฝ่ฎฐๅฝ•ไบ†ๆ—ฅๅฟ— - - [ ] ไธšๅŠก้€ป่พ‘่€ƒ่™‘ไบ†ๆ‰€ๆœ‰่พน็•Œๆƒ…ๅ†ต + - [ ] ๆ–‡ไปถๅคดๆณจ้‡ŠๅŒ…ๅซๆžถๆž„ๅฑ‚็บง่ฏดๆ˜Ž + - [ ] ็ฑปๆณจ้‡Š่ฏดๆ˜Ž่Œ่ดฃๅ’Œไธป่ฆๆ–นๆณ• + - [ ] ๆ–นๆณ•ๆณจ้‡ŠๅŒ…ๅซไธšๅŠก้€ป่พ‘ๅ’ŒๆŠ€ๆœฏๅฎž็Žฐ + - [ ] ไฟฎๆ”น่ฎฐๅฝ•ไฟๆŒๆœ€่ฟ‘5ๆฌก - [ ] **ไปฃ็ ่ดจ้‡** - [ ] ๆฒกๆœ‰ๆœชไฝฟ็”จ็š„ๅฏผๅ…ฅๅ’Œๅ˜้‡ - - [ ] ๅธธ้‡ไฝฟ็”จไบ†ๆญฃ็กฎ็š„ๅ‘ฝๅ่ง„่Œƒ - - [ ] ๆ–นๆณ•้•ฟๅบฆๅˆ็†๏ผˆๅปบ่ฎฎไธ่ถ…่ฟ‡50่กŒ๏ผ‰ - - [ ] ๅ•ไธ€่Œ่ดฃๅŽŸๅˆ™๏ผŒๆฏไธชๆ–นๆณ•ๅชๅšไธ€ไปถไบ‹ + - [ ] ๅธธ้‡ไฝฟ็”จๆญฃ็กฎๅ‘ฝๅ๏ผˆUPPER_SNAKE_CASE๏ผ‰ + - [ ] ๆ–นๆณ•้•ฟๅบฆๅˆ็†๏ผˆไธ่ถ…่ฟ‡50่กŒ๏ผ‰ + - [ ] ๅ•ไธ€่Œ่ดฃๅŽŸๅˆ™ -- [ ] **ๅฎ‰ๅ…จๆ€ง** - - [ ] ๆ•ๆ„Ÿไฟกๆฏไธๅœจๆ—ฅๅฟ—ไธญๆšด้œฒ - - [ ] ็”จๆˆท่พ“ๅ…ฅ้ƒฝ่ฟ›่กŒไบ†้ชŒ่ฏๅ’Œๆธ…็† - - [ ] ๆƒ้™ๆฃ€ๆŸฅๅœจ้€‚ๅฝ“็š„ไฝ็ฝฎ่ฟ›่กŒ +- [ ] **ๆ—ฅๅฟ—่ง„่Œƒ** + - [ ] ๅ…ณ้”ฎๆ“ไฝœ่ฎฐๅฝ•ๆ—ฅๅฟ— + - [ ] ไฝฟ็”จ็ป“ๆž„ๅŒ–ๆ—ฅๅฟ—ๆ ผๅผ + - [ ] ๆ•ๆ„Ÿไฟกๆฏๅทฒ้š่— + - [ ] ๆ—ฅๅฟ—็บงๅˆซไฝฟ็”จๆญฃ็กฎ + +- [ ] **ๅผ‚ๅธธๅค„็†** + - [ ] ๆ‰€ๆœ‰ๅผ‚ๅธธๆƒ…ๅ†ต้ƒฝๅค„็† + - [ ] ๅผ‚ๅธธ็ฑปๅž‹ไฝฟ็”จๆญฃ็กฎ + - [ ] ้”™่ฏฏไฟกๆฏๆธ…ๆ™ฐๆ˜Ž็กฎ + - [ ] ่ฎฐๅฝ•ไบ†้”™่ฏฏๆ—ฅๅฟ— --- ## ๐Ÿ’ก ๆœ€ไฝณๅฎž่ทต -### 1. ๆณจ้‡Š้ฉฑๅŠจๅผ€ๅ‘ +### 1. ้ตๅพชๅ››ๅฑ‚ๆžถๆž„ ```typescript -/** - * ็”จๆˆทๆณจๅ†ŒๅŠŸ่ƒฝ - * - * ไธšๅŠก้€ป่พ‘๏ผš - * 1. ้ชŒ่ฏ้‚ฎ็ฎฑๆ ผๅผๅ’Œๅ”ฏไธ€ๆ€ง - * 2. ้ชŒ่ฏๅฏ†็ ๅผบๅบฆ - * 3. ็”Ÿๆˆ้‚ฎ็ฎฑ้ชŒ่ฏ็  - * 4. ๅˆ›ๅปบ็”จๆˆท่ฎฐๅฝ• - * 5. ๅ‘้€้ชŒ่ฏ้‚ฎไปถ - * 6. ่ฟ”ๅ›žๆณจๅ†Œ็ป“ๆžœ - * - * @param registerData ๆณจๅ†Œๆ•ฐๆฎ - * @returns ๆณจๅ†Œ็ป“ๆžœ - */ -async registerUser(registerData: RegisterDto): Promise { - // ๅ…ˆๅ†™ๆณจ้‡Š๏ผŒๅ†ๅ†™ๅฎž็Žฐ - // ่ฟ™ๆ ท็กฎไฟ้€ป่พ‘ๆธ…ๆ™ฐ๏ผŒไธ้—ๆผๆญฅ้ชค +// โœ… ๆญฃ็กฎ๏ผšๆธ…ๆ™ฐ็š„ๅฑ‚ๆฌก่ฐƒ็”จ +// Gateway โ†’ Business โ†’ Core โ†’ Data + +// Gateway Layer +@Controller('users') +export class UsersController { + constructor(private readonly usersService: UsersService) {} + + @Get(':id') + async getUser(@Param('id') id: string) { + return this.usersService.getUserById(id); + } +} + +// Business Layer +@Injectable() +export class UsersService { + constructor(private readonly usersCoreService: UsersCoreService) {} + + async getUserById(id: string): Promise> { + try { + const user = await this.usersCoreService.findUserById(id); + return { success: true, data: user }; + } catch (error) { + return { success: false, message: error.message }; + } + } +} + +// Core Layer +@Injectable() +export class UsersCoreService { + constructor( + @Inject('IUsersService') + private readonly usersDataService: IUsersService + ) {} + + async findUserById(id: string): Promise { + const user = await this.usersDataService.findOne(id); + if (!user) { + throw new NotFoundException('็”จๆˆทไธๅญ˜ๅœจ'); + } + return user; + } } ``` -### 2. ้”™่ฏฏไผ˜ๅ…ˆๅค„็† +### 2. ไฝฟ็”จไพ่ต–ๆณจๅ…ฅๆŽฅๅฃ ```typescript -async processPayment(paymentData: PaymentDto): Promise { - // 1. ๅ…ˆๅค„็†ๆ‰€ๆœ‰ๅฏ่ƒฝ็š„้”™่ฏฏๆƒ…ๅ†ต - if (!paymentData.amount || paymentData.amount <= 0) { - throw new BadRequestException('ๆ”ฏไป˜้‡‘้ขๅฟ…้กปๅคงไบŽ0'); - } - - if (!paymentData.userId) { - throw new BadRequestException('็”จๆˆทIDไธ่ƒฝไธบ็ฉบ'); - } - - const user = await this.usersService.findOne(paymentData.userId); - if (!user) { - throw new NotFoundException('็”จๆˆทไธๅญ˜ๅœจ'); - } - - // 2. ๅ†ๅค„็†ๆญฃๅธธ็š„ไธšๅŠก้€ป่พ‘ - return this.executePayment(paymentData); +// โœ… ๆญฃ็กฎ๏ผšไฝฟ็”จๆŽฅๅฃไพ่ต–ๆณจๅ…ฅ +@Injectable() +export class LoginCoreService { + constructor( + @Inject('IUsersService') + private readonly usersService: IUsersService, + @Inject('IRedisService') + private readonly redisService: IRedisService, + ) {} +} + +// โŒ ้”™่ฏฏ๏ผš็›ดๆŽฅไพ่ต–ๅ…ทไฝ“ๅฎž็Žฐ +@Injectable() +export class LoginCoreService { + constructor( + private readonly usersService: UsersService, + private readonly redisService: RealRedisService, + ) {} } ``` -### 3. ๆ—ฅๅฟ—้ฉฑๅŠจ่ฐƒ่ฏ• +### 3. ็ปŸไธ€ๅ“ๅบ”ๆ ผๅผ ```typescript -async complexBusinessLogic(data: ComplexData): Promise { - this.logger.debug('ๅผ€ๅง‹ๆ‰ง่กŒๅคๆ‚ไธšๅŠก้€ป่พ‘', { data }); - +// ๅฎšไน‰็ปŸไธ€็š„ๅ“ๅบ”ๆŽฅๅฃ +export interface ApiResponse { + success: boolean; + data?: T; + message: string; + error_code?: string; +} + +// Business Layer ่ฟ”ๅ›ž็ปŸไธ€ๆ ผๅผ +async login(dto: LoginDto): Promise> { try { - // ๆญฅ้ชค1 - const step1Result = await this.step1(data); - this.logger.debug('ๆญฅ้ชค1ๅฎŒๆˆ', { step1Result }); - - // ๆญฅ้ชค2 - const step2Result = await this.step2(step1Result); - this.logger.debug('ๆญฅ้ชค2ๅฎŒๆˆ', { step2Result }); - - // ๆญฅ้ชค3 - const finalResult = await this.step3(step2Result); - this.logger.info('ๅคๆ‚ไธšๅŠก้€ป่พ‘ๆ‰ง่กŒๆˆๅŠŸ', { finalResult }); - - return finalResult; + const result = await this.loginCoreService.validateUser(dto); + return { + success: true, + data: result, + message: '็™ปๅฝ•ๆˆๅŠŸ' + }; } catch (error) { - this.logger.error('ๅคๆ‚ไธšๅŠก้€ป่พ‘ๆ‰ง่กŒๅคฑ่ดฅ', { data, error: error.message }); - throw error; + return { + success: false, + message: error.message, + error_code: 'LOGIN_FAILED' + }; } } ``` -### 4. ็‰ˆๆœฌ็ฎก็†ๆœ€ไฝณๅฎž่ทต +### 4. ้˜ฒๅพกๆ€ง็ผ–็จ‹ ```typescript -/** - * ็”จๆˆทๆœๅŠก - * - * ๆœ€่ฟ‘ไฟฎๆ”น๏ผš - * - 2025-01-07: ๅŠŸ่ƒฝๆ–ฐๅขž - ๆทปๅŠ ็”จๆˆทๅคดๅƒไธŠไผ ๅŠŸ่ƒฝ (v1.2.0) - * - 2025-01-06: Bugไฟฎๅค - ไฟฎๅค้‚ฎ็ฎฑ้ชŒ่ฏ้€ป่พ‘้”™่ฏฏ (v1.1.1) - * - 2025-01-05: ไปฃ็ ่ง„่Œƒไผ˜ๅŒ– - ็ปŸไธ€ๅผ‚ๅธธๅค„็†ๆ ผๅผ (v1.1.0) - * - 2025-01-04: ๅŠŸ่ƒฝๆ–ฐๅขž - ๆทปๅŠ ็”จๆˆท็Šถๆ€็ฎก็† (v1.1.0) - * - 2025-01-03: ้‡ๆž„ - ้‡ๆž„็”จๆˆท่ฎค่ฏ้€ป่พ‘ (v2.0.0) - * - * @version 1.2.0 - * @lastModified 2025-01-07 - */ +async processPayment(dto: PaymentDto): Promise> { + // 1. ๅ‚ๆ•ฐ้ชŒ่ฏ + if (!dto.amount || dto.amount <= 0) { + return { + success: false, + message: 'ๆ”ฏไป˜้‡‘้ขๅฟ…้กปๅคงไบŽ0', + error_code: 'INVALID_AMOUNT' + }; + } + + // 2. ไธšๅŠก่ง„ๅˆ™้ชŒ่ฏ + const user = await this.usersService.findOne(dto.userId); + if (!user) { + return { + success: false, + message: '็”จๆˆทไธๅญ˜ๅœจ', + error_code: 'USER_NOT_FOUND' + }; + } + + // 3. ็Šถๆ€ๆฃ€ๆŸฅ + if (user.status !== UserStatus.ACTIVE) { + return { + success: false, + message: '็”จๆˆท็Šถๆ€ไธๅ…่ฎธๆ”ฏไป˜', + error_code: 'USER_INACTIVE' + }; + } + + // 4. ๆ‰ง่กŒไธšๅŠก้€ป่พ‘ + return this.executePayment(dto); +} ``` -**ไฟฎๆ”น่ฎฐๅฝ•็ฎก็†ๅŽŸๅˆ™๏ผš** -- โœ… **ไฟๆŒ็ฎ€ๆด** - ๅชไฟ็•™ๆœ€่ฟ‘5ๆฌกไฟฎๆ”น -- โœ… **ๅฎšๆœŸๆธ…็†** - ่ถ…ๅ‡บ5ๆกๆ—ถๅˆ ้™คๆœ€ๆ—ง่ฎฐๅฝ• -- โœ… **้‡่ฆๆ ‡ๆณจ** - ้‡ๅคง็‰ˆๆœฌๆ›ดๆ–ฐๅฏ็‰นๅˆซๆ ‡ๆณจ็‰ˆๆœฌๅท -- โœ… **ๆ่ฟฐๆธ…ๆ™ฐ** - ๆฏๆก่ฎฐๅฝ•้ƒฝ่ฆ่ฏดๆ˜Žๅ…ทไฝ“ๆ”นๅŠจๅ†…ๅฎน +### 5. ๆต‹่ฏ•้ฉฑๅŠจๅผ€ๅ‘ + +```typescript +// ๅ…ˆๅ†™ๆต‹่ฏ• +describe('LoginService', () => { + it('should login successfully with valid credentials', async () => { + const dto = { identifier: 'test@example.com', password: 'password123' }; + const result = await loginService.login(dto); + + expect(result.success).toBe(true); + expect(result.data).toHaveProperty('accessToken'); + }); + + it('should return error with invalid credentials', async () => { + const dto = { identifier: 'test@example.com', password: 'wrong' }; + const result = await loginService.login(dto); + + expect(result.success).toBe(false); + expect(result.error_code).toBe('LOGIN_FAILED'); + }); +}); + +// ๅ†ๅ†™ๅฎž็Žฐ +@Injectable() +export class LoginService { + async login(dto: LoginDto): Promise> { + // ๅฎž็Žฐ้€ป่พ‘ + } +} +``` --- ## ๐ŸŽฏ ๆ€ป็ป“ -้ตๅพชๅŽ็ซฏๅผ€ๅ‘่ง„่Œƒ่ƒฝๅคŸ๏ผš +้ตๅพชๅผ€ๅ‘่ง„่Œƒ่ƒฝๅคŸ๏ผš -1. **ๆ้ซ˜ไปฃ็ ่ดจ้‡** - ้€š่ฟ‡ๅฎŒๆ•ด็š„ๆณจ้‡Šๅ’Œ่ง„่Œƒ็š„ๅฎž็Žฐ -2. **ๆๅ‡ๅ›ข้˜Ÿๆ•ˆ็އ** - ็ปŸไธ€็š„่ง„่Œƒๅ‡ๅฐ‘ๆฒŸ้€šๆˆๆœฌ -3. **้™ไฝŽ็ปดๆŠคๆˆๆœฌ** - ๆธ…ๆ™ฐ็š„ๆ–‡ๆกฃๅ’Œๆ—ฅๅฟ—ไพฟไบŽ้—ฎ้ข˜ๅฎšไฝ -4. **ๅขžๅผบ็ณป็ปŸ็จณๅฎšๆ€ง** - ๅฎŒๅ–„็š„ๅผ‚ๅธธๅค„็†ๅ’Œ้˜ฒๅพกๆ€ง็ผ–็จ‹ -5. **ไฟƒ่ฟ›็Ÿฅ่ฏ†ไผ ๆ‰ฟ** - ่ฏฆ็ป†็š„ไฟฎๆ”น่ฎฐๅฝ•ๅ’Œ็‰ˆๆœฌ็ฎก็† +1. **ๆธ…ๆ™ฐ็š„ๆžถๆž„** - ๅ››ๅฑ‚ๆžถๆž„็กฎไฟ่Œ่ดฃๅˆ†็ฆป +2. **้ซ˜่ดจ้‡ไปฃ็ ** - ๅฎŒๆ•ด็š„ๆณจ้‡Šๅ’Œ่ง„่Œƒ็š„ๅฎž็Žฐ +3. **ๆ˜“ไบŽ็ปดๆŠค** - ๆธ…ๆ™ฐ็š„ๆ–‡ๆกฃๅ’Œๆ—ฅๅฟ—ไพฟไบŽ้—ฎ้ข˜ๅฎšไฝ +4. **ๅ›ข้˜Ÿๅไฝœ** - ็ปŸไธ€็š„่ง„่Œƒๅ‡ๅฐ‘ๆฒŸ้€šๆˆๆœฌ +5. **็ณป็ปŸ็จณๅฎš** - ๅฎŒๅ–„็š„ๅผ‚ๅธธๅค„็†ๅ’Œ้˜ฒๅพกๆ€ง็ผ–็จ‹ -**่ฎฐไฝ๏ผšๅฅฝ็š„ไปฃ็ ไธไป…่ฆ่ƒฝ่ฟ่กŒ๏ผŒๆ›ด่ฆ่ƒฝ่ขซ็†่งฃใ€็ปดๆŠคๅ’Œๆ‰ฉๅฑ•ใ€‚** +**่ฎฐไฝ๏ผšๅฅฝ็š„ไปฃ็ ไธไป…่ฆ่ƒฝ่ฟ่กŒ๏ผŒๆ›ด่ฆ็ฌฆๅˆๆžถๆž„่ฎพ่ฎกใ€ๆ˜“ไบŽ็†่งฃใ€ไพฟไบŽ็ปดๆŠคๅ’Œๆ‰ฉๅฑ•ใ€‚** --- ## ๐Ÿ“š ็›ธๅ…ณๆ–‡ๆกฃ -- [ๅ‘ฝๅ่ง„่Œƒ](./naming_convention.md) - ไปฃ็ ๅ‘ฝๅ่ง„่Œƒ -- [NestJS ไฝฟ็”จๆŒ‡ๅ—](./nestjs_guide.md) - ๆก†ๆžถๆœ€ไฝณๅฎž่ทต -- [Git ๆไบค่ง„่Œƒ](./git_commit_guide.md) - ็‰ˆๆœฌๆŽงๅˆถ่ง„่Œƒ -- [AI ่พ…ๅŠฉๅผ€ๅ‘่ง„่Œƒ](./AI่พ…ๅŠฉๅผ€ๅ‘่ง„่ŒƒๆŒ‡ๅ—.md) - AI ่พ…ๅŠฉๅผ€ๅ‘ๆŒ‡ๅ— \ No newline at end of file +- [ๆžถๆž„่ฎพ่ฎกๆ–‡ๆกฃ](../ARCHITECTURE.md) - ๅ››ๅฑ‚ๆžถๆž„่ฏฆ่งฃ +- [ๆžถๆž„้‡ๆž„ๆ–‡ๆกฃ](../ARCHITECTURE_REFACTORING.md) - ๆžถๆž„่ฟ็งปๆŒ‡ๅ— +- [Gitๆไบค่ง„่Œƒ](./git_commit_guide.md) - ็‰ˆๆœฌๆŽงๅˆถ่ง„่Œƒ +- [ๆต‹่ฏ•ๆŒ‡ๅ—](./TESTING.md) - ๆต‹่ฏ•่ง„่Œƒๅ’Œๆœ€ไฝณๅฎž่ทต