ๆˆ‘็”จ NestJS + Vue3 + Prisma + PostgreSQL ๆ‰“้€ ไบ†ไธ€ไธชไผไธš็บง sass ๅคš็งŸๆˆทๅนณๅฐ

Source: Juejin Frontend Hottest

๐ŸŽฏ ๆˆ‘็”จ NestJS + Vue3 + Prisma + PostgreSQL ๆ‰“้€ ไบ†ไธ€ไธชไผไธš็บง sass ๅคš็งŸๆˆทๅนณๅฐ

ไธ€ไธช็”Ÿไบง็บง็š„ๅ…จๆ ˆ็ฎก็†็ณป็ปŸๅผ€ๆบ้กน็›ฎ๏ผŒไปŽ้›ถๅˆฐไธ€็š„ๅฎžๆˆ˜ๅˆ†ไบซ

ๅ‰่จ€

่ฟ™ๆ˜ฏไธ€ไธชๅŸบไบŽ NestJS + Vue3 + Prisma + PostgreSQL ** ๆž„ๅปบ็š„ๅ…จๆ ˆ็ฎก็†็ณป็ปŸ๏ผŒไธๆ˜ฏ็ฎ€ๅ•็š„ CRUD๏ผŒ่€Œๆ˜ฏไธ€ไธช็”Ÿไบง็บงๅˆซ**็š„่งฃๅ†ณๆ–นๆกˆใ€‚ๅฎƒๅŒ…ๅซไบ†ๅคš็งŸๆˆทๆžถๆž„ใ€RBACๆƒ้™็ฎก็†ใ€่ฏทๆฑ‚ๅŠ ๅฏ†ใ€ๅฎŒๅ–„็š„ๆ—ฅๅฟ—็›‘ๆŽง็ญ‰ไผไธš็บง็‰นๆ€งใ€‚


๐Ÿ”— ้กน็›ฎ้“พๆŽฅ

๐ŸŒŸ GitHub ๅผ€ๆบๅœฐๅ€๏ผšgithub.com/linlingqin7โ€ฆ
๐ŸŽฎ ๅœจ็บฟไฝ“้ชŒๅœฐๅ€๏ผšwww.linlingqin.top/
๐Ÿ“– ้กน็›ฎๆ–‡ๆกฃๅœฐๅ€๏ผš้กน็›ฎๆ–‡ๆกฃ

ไฝ“้ชŒ่ดฆๅท๏ผšdemo / demo123 (็งŸๆˆท 000000)
ๅฎŒๆ•ดๆƒ้™่ดฆๅท๏ผšadmin / admin123 (็งŸๆˆท 000000)


๐Ÿ“ฑ ๅ…ˆ็œ‹ๆ•ˆๆžœ

็™ปๅฝ•้กต้ข

login.png

ๆ”ฏๆŒ่ดฆๅทๅฏ†็ ็™ปๅฝ•ใ€้ชŒ่ฏ็ ้ชŒ่ฏใ€่ฎฐไฝๅฏ†็ 

้ฆ–้กตไปช่กจๆฟ

dashboard.png

็ณป็ปŸๆฆ‚่งˆใ€ๅฟซๆทๅ…ฅๅฃใ€ๆ•ฐๆฎ็ปŸ่ฎกใ€ๅ›พ่กจๅฑ•็คบ

็”จๆˆท็ฎก็†

user.png

็”จๆˆทๅˆ—่กจใ€่ง’่‰ฒๅˆ†้…ใ€้ƒจ้—จ้€‰ๆ‹ฉใ€็Šถๆ€็ฎก็†

่ง’่‰ฒ็ฎก็†

role.png

่ง’่‰ฒๆƒ้™้…็ฝฎใ€่œๅ•ๆƒ้™ๆ ‘ใ€ๆ•ฐๆฎๆƒ้™่Œƒๅ›ด

  • โœจ ็ŽฐไปฃๅŒ–็š„็•Œ้ข่ฎพ่ฎก๏ผŒๆ”ฏๆŒๆทฑ่‰ฒๆจกๅผ
  • ๐Ÿ“ฑ ๅ“ๅบ”ๅผๅธƒๅฑ€๏ผŒๅฎŒ็พŽ้€‚้…ๅ„็งๅฑๅน•
  • ๐ŸŽจ ไธฐๅฏŒ็š„็ป„ไปถๅ’Œไบคไบ’ๆ•ˆๆžœ

๏ฟฝ ๅฎŒๆ•ดๅŠŸ่ƒฝๆˆชๅ›พ

่œๅ•็ฎก็†

menu.png

่œๅ•ๆ ‘ๅฝข็ป“ๆž„ใ€่ทฏ็”ฑ้…็ฝฎใ€ๅ›พๆ ‡้€‰ๆ‹ฉใ€ๆƒ้™ๆ ‡่ฏ†

้ƒจ้—จ็ฎก็†

dept.png

็ป„็ป‡ๆžถๆž„ๆ ‘ใ€้ƒจ้—จไบบๅ‘˜ใ€ๆ•ฐๆฎๆƒ้™

ๅฒ—ไฝ็ฎก็†

post.png

ๅฒ—ไฝ้…็ฝฎใ€ๅฒ—ไฝๆŽ’ๅบใ€็Šถๆ€็ฎก็†

ๅญ—ๅ…ธ็ฎก็†

dict.png

ๆ•ฐๆฎๅญ—ๅ…ธ็ปดๆŠคใ€ๅญ—ๅ…ธ้กน้…็ฝฎ

ๅ‚ๆ•ฐ้…็ฝฎ

paramSet.png

็ณป็ปŸๅ‚ๆ•ฐใ€ๅŠจๆ€้…็ฝฎใ€ๅ‚ๆ•ฐๅˆ†็ฑป

้€š็Ÿฅๅ…ฌๅ‘Š

notice.png

ๅ…ฌๅ‘Šๅ‘ๅธƒใ€้€š็Ÿฅ็ฎก็†ใ€็ฑปๅž‹ๅˆ†็ฑป

็งŸๆˆท็ฎก็†

tenant.png

ๅคš็งŸๆˆทๅˆ—่กจใ€ๅฅ—้ค้…็ฝฎใ€็งŸๆˆท็Šถๆ€

ๅฎšๆ—ถไปปๅŠก

job.png

Cron ไปปๅŠก้…็ฝฎใ€ๆ‰ง่กŒๆ—ฅๅฟ—ใ€ไปปๅŠก็ฎก็†

็ณป็ปŸ็›‘ๆŽง

monitor.png

ๆœๅŠกๅ™จ็Šถๆ€ใ€CPU/ๅ†…ๅญ˜ไฝฟ็”จ็އใ€็ฃ็›˜ไฟกๆฏ

็ผ“ๅญ˜็›‘ๆŽง

monitorCache.png

Redis ็ผ“ๅญ˜ไฟกๆฏใ€ๅ‘ฝไปค็ปŸ่ฎกใ€้”ฎๅ€ผ็ฎก็†

ๅœจ็บฟ็”จๆˆท

online.png

ๅฎžๆ—ถๅœจ็บฟ็”จๆˆทใ€ไผš่ฏ็ฎก็†ใ€ๅผบๅˆถไธ‹็บฟ

ๆ“ไฝœๆ—ฅๅฟ—

operlog.png

ๆ“ไฝœ่ฎฐๅฝ•ใ€่ฏทๆฑ‚ๅ‚ๆ•ฐใ€ๅ“ๅบ”็ป“ๆžœใ€ๅผ‚ๅธธๆ•่Žท

็™ปๅฝ•ๆ—ฅๅฟ—

loginLog.png

็™ปๅฝ•ๅކๅฒใ€IP ๅœฐๅ€ใ€ๆต่งˆๅ™จไฟกๆฏใ€็™ปๅฝ•็Šถๆ€

็ผ“ๅญ˜ๅˆ—่กจ

cacheList.png

็ผ“ๅญ˜้”ฎ็ฎก็†ใ€่ฟ‡ๆœŸๆ—ถ้—ดใ€็ผ“ๅญ˜ๆธ…็†

ไธป้ข˜้…็ฝฎ

theme.png

ๅคšไธป้ข˜ๅˆ‡ๆขใ€ๆทฑ่‰ฒๆจกๅผใ€ไธป้ข˜่‰ฒ้…็ฝฎใ€ๅธƒๅฑ€ๆจกๅผ

โœจ ๆ ธๅฟƒ็‰นๆ€ง

๐Ÿข ๅคš็งŸๆˆท SaaS ๆžถๆž„

่ฟ™ๆ˜ฏๆœฌ้กน็›ฎ็š„ไธ€ๅคงไบฎ็‚นใ€‚ๅฎž็Žฐไบ†ๅฎŒๆ•ด็š„็งŸๆˆทๆ•ฐๆฎ้š”็ฆป๏ผš

// ๆ‰€ๆœ‰ๆ•ฐๆฎๅบ“ๆŸฅ่ฏข่‡ชๅŠจๆŒ‰็งŸๆˆท่ฟ‡ๆปค
const users = await prisma.sysUser.findMany({
  where: { name: 'John' }
  // tenantId ไผš่‡ชๅŠจๆณจๅ…ฅ๏ผŒๆ— ้œ€ๆ‰‹ๅŠจๆทปๅŠ 
});

// ่ทณ่ฟ‡็งŸๆˆท่ฟ‡ๆปค๏ผˆ็‰นๆฎŠๅœบๆ™ฏ๏ผ‰
@IgnoreTenant()
async getAllTenants() {
  return await this.prisma.tenant.findMany();
}

ๆŠ€ๆœฏๅฎž็Žฐ๏ผš

  • ๅŸบไบŽ Prisma Extension ๅฎž็Žฐ้€ๆ˜Ž็š„็งŸๆˆท่ฟ‡ๆปค
  • ้€š่ฟ‡ TenantGuard ไปŽ่ฏทๆฑ‚ๅคด่‡ชๅŠจ่ฏ†ๅˆซ็งŸๆˆท
  • ่ถ…็บง็ฎก็†ๅ‘˜๏ผˆ็งŸๆˆท 000000๏ผ‰ๅฏ่ทจ็งŸๆˆท็ฎก็†

้€‚็”จๅœบๆ™ฏ๏ผš

  • SaaS ๅนณๅฐ
  • ไผไธšๅ†…้ƒจๅคš้ƒจ้—จ็ณป็ปŸ
  • ็™ฝๆ ‡ไบงๅ“

๐Ÿ” RBAC ๆƒ้™็ฎก็†

ไธๅชๆ˜ฏ็ฎ€ๅ•็š„่ง’่‰ฒๆƒ้™๏ผŒ่€Œๆ˜ฏๅคšๅฑ‚็บงใ€็ป†็ฒ’ๅบฆ็š„ๆƒ้™ๆŽงๅˆถ๏ผš

@Controller('users')
export class UserController {
  // ้œ€่ฆ็‰นๅฎšๆƒ้™ๆ‰่ƒฝ่ฎฟ้—ฎ
  @RequirePermission('system:user:add')
  @Post()
  create(@Body() dto: CreateUserDto) {
    return this.userService.create(dto);
  }
  
  // ้œ€่ฆ็‰นๅฎš่ง’่‰ฒ
  @RequireRole('admin')
  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.userService.remove(id);
  }
}

ๆƒ้™ๅฑ‚็บง๏ผš

  1. ่œๅ•็บงๅˆซ๏ผšๆŽงๅˆถ่œๅ•ๆ˜พ็คบ/้š่—
  2. ๆŒ‰้’ฎ็บงๅˆซ๏ผšๆŽงๅˆถ้กต้ขๅ†…ๆŒ‰้’ฎๆƒ้™
  3. ๆ•ฐๆฎ็บงๅˆซ๏ผšๅ…จ้ƒจ/ๆœฌ้ƒจ้—จ/ไป…ๆœฌไบบ็ญ‰ๆ•ฐๆฎ่Œƒๅ›ด

ๅ‰็ซฏๆƒ้™ๆŽงๅˆถ๏ผš

<template>
  <!-- ๆŒ‰้’ฎ็บงๆƒ้™ -->
  <n-button v-if="hasPermission('system:user:add')">
    ๆทปๅŠ ็”จๆˆท
  </n-button>
</template>

๐Ÿ”’ ่ฏทๆฑ‚ๅŠ ๅฏ†ๆœบๅˆถ

ๆ•ๆ„Ÿๆ•ฐๆฎไผ ่พ“้‡‡็”จ AES + RSA ๆททๅˆๅŠ ๅฏ†๏ผš

ๅŠ ๅฏ†ๆต็จ‹๏ผš

  1. ๅ‰็ซฏ็”Ÿๆˆ้šๆœบ AES ๅฏ†้’ฅ
  2. ็”จ AES-CBC ๅŠ ๅฏ†่ฏทๆฑ‚ๆ•ฐๆฎ
  3. ็”จๆœๅŠก็ซฏ RSA ๅ…ฌ้’ฅๅŠ ๅฏ† AES ๅฏ†้’ฅ
  4. ๅ‘้€ {encryptedKey, encryptedData} + header x-encrypted: true
// ๅŽ็ซฏ่‡ชๅŠจ่งฃๅฏ†
@Post('login')
async login(@Body() dto: LoginDto) {
  // dto ๅทฒ่‡ชๅŠจ่งฃๅฏ†๏ผŒ็›ดๆŽฅไฝฟ็”จ
  return this.authService.login(dto);
}

// ่ทณ่ฟ‡่งฃๅฏ†๏ผˆ้žๆ•ๆ„ŸๆŽฅๅฃ๏ผ‰
@SkipDecrypt()
@Get('captcha')
async getCaptcha() {
  return this.captchaService.generate();
}

ไผ˜ๅŠฟ๏ผš

  • ไฟๆŠคๅฏ†็ ใ€Token ็ญ‰ๆ•ๆ„Ÿไฟกๆฏ
  • ้˜ฒๆญขไธญ้—ดไบบๆ”ปๅ‡ป
  • ๅฏนไธšๅŠกไปฃ็ ้€ๆ˜Ž๏ผŒ็”ฑๆ‹ฆๆˆชๅ™จ็ปŸไธ€ๅค„็†

๐Ÿ“Š ๅฎŒๅ–„็š„ๆ—ฅๅฟ—็›‘ๆŽง

ๅŸบไบŽ Pino ๅฎž็Žฐ็š„้ซ˜ๆ€ง่ƒฝ็ป“ๆž„ๅŒ–ๆ—ฅๅฟ—๏ผš

// ่‡ชๅŠจ่ฎฐๅฝ•ๆ“ไฝœๆ—ฅๅฟ—
@Operlog({
  businessType: BusinessTypeEnum.UPDATE,
  title: 'ไฟฎๆ”น็”จๆˆท'
})
@Put(':id')
async update(@Param('id') id: string, @Body() dto: UpdateUserDto) {
  return this.userService.update(id, dto);
}

็›‘ๆŽง่ƒฝๅŠ›๏ผš

  • ็ป“ๆž„ๅŒ–ๆ—ฅๅฟ—๏ผš่‡ชๅŠจ่ฎฐๅฝ• requestIdใ€tenantIdใ€username
  • ๆ•ๆ„Ÿๅญ—ๆฎต่„ฑๆ•๏ผšpasswordใ€token ็ญ‰่‡ชๅŠจ้š่—
  • ๆ“ไฝœๅฎก่ฎก๏ผš่ฎฐๅฝ•่ฐๅœจไป€ไนˆๆ—ถ้—ดๅšไบ†ไป€ไนˆ
  • ็™ปๅฝ•ๆ—ฅๅฟ—๏ผš็™ปๅฝ•ๅކๅฒใ€IPใ€ๆต่งˆๅ™จ็ญ‰ไฟกๆฏ
  • ๅฅๅบทๆฃ€ๆŸฅ๏ผšK8s liveness/readiness ๆŽข้’ˆ
  • Prometheus ๆŒ‡ๆ ‡๏ผšๆšด้œฒ /api/metrics ็ซฏ็‚น

ๆ—ฅๅฟ—่พ“ๅ‡บ็คบไพ‹๏ผš

{
  "level": "info",
  "time": "2025-12-22T10:30:00.000Z",
  "requestId": "a1b2c3d4",
  "tenantId": "000001",
  "username": "admin",
  "method": "POST",
  "url": "/api/system/user",
  "statusCode": 200,
  "duration": 45
}

๐ŸŽญ ๆผ”็คบ่ดฆๆˆท็ณป็ปŸ

ไธ“ไธบไบงๅ“ๆผ”็คบ่ฎพ่ฎก็š„ๅช่ฏป่ดฆๆˆทๆœบๅˆถ๏ผš

// Demo ่ดฆๆˆทๆ‹ฆๆˆชๅ™จ
@UseInterceptors(DemoInterceptor)
@Controller('users')
export class UserController {
  @Post()  // Demo ่ดฆๆˆทไผš่ขซ่‡ชๅŠจๆ‹ฆๆˆช
  create(@Body() dto: CreateUserDto) {
    return this.userService.create(dto);
  }
  
  @Get()  // ๆŸฅ่ฏขๆ“ไฝœไธๅ—ๅฝฑๅ“
  findAll() {
    return this.userService.findAll();
  }
}

็‰นๆ€ง๏ผš

  • 52 ไธชๅช่ฏปๆƒ้™๏ผŒๅฏๆŸฅ็œ‹ๆ‰€ๆœ‰ๆจกๅ—
  • ่‡ชๅŠจๆ‹ฆๆˆชๆ‰€ๆœ‰ๅ†™ๆ“ไฝœ๏ผˆPOST/PUT/DELETE/PATCH๏ผ‰
  • ่ฟ”ๅ›žๅ‹ๅฅฝ็š„ๆ็คบไฟกๆฏ
  • ้€‚ๅˆๆผ”็คบ็ซ™็‚นใ€ไบงๅ“ Demo

๐ŸŒ ๅ›ฝ้™…ๅŒ–ๆ”ฏๆŒ

ๅ‰ๅŽ็ซฏๅฎŒๆ•ด็š„ i18n ๆ–นๆกˆ๏ผš

// ๅŽ็ซฏ
throw new ApiException(ErrorEnum.USER_NOT_FOUND);
// ่‡ชๅŠจ่ฟ”ๅ›žๅฏนๅบ”่ฏญ่จ€็š„้”™่ฏฏไฟกๆฏ

// ๅ‰็ซฏ
const { t } = useI18n();
console.log(t('system.user.name')); // ๆ นๆฎ่ฏญ่จ€่ฟ”ๅ›žๅฏนๅบ”ๆ–‡ๆœฌ

ๆ”ฏๆŒไธญๆ–‡ใ€่‹ฑๆ–‡๏ผŒๅฏ่ฝปๆพๆ‰ฉๅฑ•ๅ…ถไป–่ฏญ่จ€ใ€‚


๐Ÿ› ๏ธ ๆŠ€ๆœฏๆ ˆ่ฏฆ่งฃ

ๅŽ็ซฏๆŠ€ๆœฏๆ ˆ

ๆŠ€ๆœฏ็‰ˆๆœฌๆ ธๅฟƒๅบ”็”จๅœบๆ™ฏๆŠ€ๆœฏไบฎ็‚น
NestJS10.xไผไธš็บงๆก†ๆžถ๏ผŒๆž„ๅปบๅฏๆ‰ฉๅฑ•็š„ๆœๅŠก็ซฏๅบ”็”จโ€ข ไพ่ต–ๆณจๅ…ฅ
โ€ข ๆจกๅ—ๅŒ–่ฎพ่ฎก
โ€ข ่ฃ…้ฅฐๅ™จ่ฏญๆณ•
โ€ข ๅ†…็ฝฎ TypeScript
Prisma5.x็ฑปๅž‹ๅฎ‰ๅ…จ็š„ๆ•ฐๆฎๅบ“ ORMโ€ข ่‡ชๅŠจ็”Ÿๆˆ็ฑปๅž‹
โ€ข ๆ•ฐๆฎๅบ“่ฟ็งป
โ€ข ๅผบๅคง็š„ๆŸฅ่ฏขๆž„ๅปบๅ™จ
โ€ข ๅคšๆ•ฐๆฎๅบ“ๆ”ฏๆŒ
PostgreSQL14+ไธปๆ•ฐๆฎๅบ“๏ผŒๅญ˜ๅ‚จๆ ธๅฟƒไธšๅŠกๆ•ฐๆฎโ€ข ACID ไบ‹ๅŠก
โ€ข JSON ๆ”ฏๆŒ
โ€ข ไธฐๅฏŒ็š„ๆ•ฐๆฎ็ฑปๅž‹
โ€ข ๅผบๅคง็š„ๆŸฅ่ฏขไผ˜ๅŒ–
Redis7+็ผ“ๅญ˜ใ€Sessionใ€ๅˆ†ๅธƒๅผ้”โ€ข ้ซ˜ๆ€ง่ƒฝ็ผ“ๅญ˜
โ€ข ๆ•ฐๆฎ่ฟ‡ๆœŸ็ญ–็•ฅ
โ€ข ๅ‘ๅธƒ่ฎข้˜…
โ€ข ๅˆ†ๅธƒๅผ้”
JWT-ๆ— ็Šถๆ€่บซไปฝ่ฎค่ฏโ€ข Token ่ฎค่ฏ
โ€ข Refresh Token
โ€ข ่ทจๅŸŸ่ฎค่ฏ
โ€ข ็งปๅŠจ็ซฏๅ‹ๅฅฝ
Passport-่ฎค่ฏไธญ้—ดไปถโ€ข ็ญ–็•ฅๆจกๅผ
โ€ข ๅคš็ง่ฎค่ฏๆ–นๅผ
โ€ข ๆ˜“ไบŽๆ‰ฉๅฑ•
Pino-้ซ˜ๆ€ง่ƒฝ็ป“ๆž„ๅŒ–ๆ—ฅๅฟ—โ€ข JSON ๆ—ฅๅฟ—
โ€ข ไฝŽๅผ€้”€
โ€ข ๆ—ฅๅฟ—่ฝฎ่ฝฌ
โ€ข ๅคšไผ ่พ“้€š้“
Swagger-API ๆ–‡ๆกฃ่‡ชๅŠจ็”Ÿๆˆโ€ข ไบคไบ’ๅผๆ–‡ๆกฃ
โ€ข ่‡ชๅŠจ็ฑปๅž‹ๆŽจๅฏผ
โ€ข ๅœจ็บฟๆต‹่ฏ•
Terminus-ๅฅๅบทๆฃ€ๆŸฅไธŽ็›‘ๆŽงโ€ข K8s ๆŽข้’ˆ
โ€ข ๆ•ฐๆฎๅบ“ๆฃ€ๆŸฅ
โ€ข ๅ†…ๅญ˜็›‘ๆŽง
โ€ข ่‡ชๅฎšไน‰ๆฃ€ๆŸฅ
class-validator-ๆ•ฐๆฎ้ชŒ่ฏโ€ข ่ฃ…้ฅฐๅ™จ้ชŒ่ฏ
โ€ข ่‡ชๅฎšไน‰่ง„ๅˆ™
โ€ข ๅตŒๅฅ—้ชŒ่ฏ
class-transformer-ๆ•ฐๆฎ่ฝฌๆขโ€ข DTO ่ฝฌๆข
โ€ข ็ฑปๅž‹ๆ˜ ๅฐ„
โ€ข ๆ•ฐๆฎ่„ฑๆ•
@nestjs/schedule-ๅฎšๆ—ถไปปๅŠก่ฐƒๅบฆโ€ข Cron ่กจ่พพๅผ
โ€ข ้—ด้š”ไปปๅŠก
โ€ข ่ถ…ๆ—ถๆŽงๅˆถ
nestjs-cls-่ฏทๆฑ‚ไธŠไธ‹ๆ–‡็ฎก็†โ€ข ่ฏทๆฑ‚่ฟฝ่ธช
โ€ข ็”จๆˆทไธŠไธ‹ๆ–‡
โ€ข ็งŸๆˆทไธŠไธ‹ๆ–‡

ๅ‰็ซฏๆŠ€ๆœฏๆ ˆ

ๆŠ€ๆœฏ็‰ˆๆœฌๆ ธๅฟƒๅบ”็”จๅœบๆ™ฏๆŠ€ๆœฏไบฎ็‚น
Vue 33.5+ๆธ่ฟ›ๅผๅ‰็ซฏๆก†ๆžถโ€ข Composition API
โ€ข ๅ“ๅบ”ๅผ็ณป็ปŸ
โ€ข ่™šๆ‹Ÿ DOM
โ€ข ็ป„ไปถๅŒ–ๅผ€ๅ‘
Vite7+ๆ–ฐไธ€ไปฃๆž„ๅปบๅทฅๅ…ทโ€ข ๆž้€Ÿๅ†ทๅฏๅŠจ
โ€ข HMR ็ƒญๆ›ดๆ–ฐ
โ€ข ๆŒ‰้œ€็ผ–่ฏ‘
โ€ข Rollup ๆ‰“ๅŒ…
Naive UIๆœ€ๆ–ฐไผไธš็บง็ป„ไปถๅบ“โ€ข Vue 3 ็ป„ๅˆๅผ API
โ€ข TypeScript ๆ”ฏๆŒ
โ€ข ไธป้ข˜ๅฎšๅˆถ
โ€ข 200+ ็ป„ไปถ
UnoCSSๆœ€ๆ–ฐๅณๆ—ถๅŽŸๅญๅŒ– CSS ๅผ•ๆ“Žโ€ข ้›ถ่ฟ่กŒๆ—ถ
โ€ข ้ซ˜ๆ€ง่ƒฝ
โ€ข ้ข„่ฎพ็ณป็ปŸ
โ€ข ๆŒ‰้œ€็”Ÿๆˆ
Piniaๆœ€ๆ–ฐไธ‹ไธ€ไปฃ็Šถๆ€็ฎก็†โ€ข ่ฝป้‡็บง
โ€ข TypeScript ๆ”ฏๆŒ
โ€ข ๆจกๅ—ๅŒ–
โ€ข DevTools ๆ”ฏๆŒ
Vue Router4+ๅฎ˜ๆ–น่ทฏ็”ฑ็ฎก็†โ€ข ๅŠจๆ€่ทฏ็”ฑ
โ€ข ่ทฏ็”ฑๅฎˆๅซ
โ€ข ๆ‡’ๅŠ ่ฝฝ
โ€ข ๅตŒๅฅ—่ทฏ็”ฑ
Axiosๆœ€ๆ–ฐHTTP ่ฏทๆฑ‚ๅบ“โ€ข Promise API
โ€ข ๆ‹ฆๆˆชๅ™จ
โ€ข ่ฏทๆฑ‚ๅ–ๆถˆ
โ€ข ่‡ชๅŠจ่ฝฌๆข
TypeScript5.x็ฑปๅž‹ๅฎ‰ๅ…จ็š„ JavaScriptโ€ข ้™ๆ€็ฑปๅž‹ๆฃ€ๆŸฅ
โ€ข IDE ๆ™บ่ƒฝๆ็คบ
โ€ข ้‡ๆž„ๆ”ฏๆŒ
โ€ข ๆŽฅๅฃๅฎšไน‰
VueUseๆœ€ๆ–ฐVue ็ป„ๅˆๅผๅ‡ฝๆ•ฐ้›†ๅˆโ€ข ๅธธ็”จ Hooks
โ€ข ๅ“ๅบ”ๅผๅทฅๅ…ท
โ€ข ๆต่งˆๅ™จ API ๅฐ่ฃ…
Elegant Routerๆœ€ๆ–ฐๅŸบไบŽๆ–‡ไปถ็š„่ทฏ็”ฑ็ณป็ปŸโ€ข ่‡ชๅŠจ็”Ÿๆˆ่ทฏ็”ฑ
โ€ข ็บฆๅฎšๅผ่ทฏ็”ฑ
โ€ข ็ฑปๅž‹ๅฎ‰ๅ…จ
ECharts5+ๆ•ฐๆฎๅฏ่ง†ๅŒ–ๅ›พ่กจโ€ข ไธฐๅฏŒ็š„ๅ›พ่กจ็ฑปๅž‹
โ€ข ๅ“ๅบ”ๅผ่ฎพ่ฎก
โ€ข ไธป้ข˜ๅฎšๅˆถ
CryptoJS-ๅŠ ๅฏ†็ฎ—ๆณ•ๅบ“โ€ข AES ๅŠ ๅฏ†
โ€ข RSA ๅŠ ๅฏ†
โ€ข MD5/SHA ๅ“ˆๅธŒ

๐Ÿ—๏ธ ็ณป็ปŸๆžถๆž„่ฏฆ่งฃ

๐Ÿ“ ๆ•ดไฝ“ๆžถๆž„ๅ›พ

                        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                        โ”‚      ็”จๆˆท/ๅฎขๆˆท็ซฏๅฑ‚              โ”‚
                        โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
                        โ”‚  โ”‚  ๆต่งˆๅ™จ   โ”‚  โ”‚  ็งปๅŠจ็ซฏApp  โ”‚ โ”‚
                        โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
                        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                       โ”‚ HTTPS
                        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                        โ”‚      CDN / Nginx ็ฝ‘ๅ…ณ            โ”‚
                        โ”‚  โ€ข ้™ๆ€่ต„ๆบ็ผ“ๅญ˜                  โ”‚
                        โ”‚  โ€ข ๅๅ‘ไปฃ็†                      โ”‚
                        โ”‚  โ€ข ่ดŸ่ฝฝๅ‡่กก                      โ”‚
                        โ”‚  โ€ข SSL ่ฏไนฆ                      โ”‚
                        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                       โ”‚
                โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                โ”‚                                              โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”       โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚        ๅ‰็ซฏๅบ”็”จ (Vue3)          โ”‚       โ”‚     ๅŽ็ซฏๅบ”็”จ (NestJS)         โ”‚
โ”‚                                 โ”‚       โ”‚                               โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚       โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚     UI ๅฑ‚ (Naive UI)     โ”‚  โ”‚       โ”‚  โ”‚   ๆŽงๅˆถๅ™จๅฑ‚ (Controllers)โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ็ป„ไปถๅบ“                โ”‚  โ”‚       โ”‚  โ”‚   โ€ข ่ทฏ็”ฑๅฎšไน‰           โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ไธป้ข˜ๅฎšๅˆถ              โ”‚  โ”‚       โ”‚  โ”‚   โ€ข ่ฏทๆฑ‚้ชŒ่ฏ           โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ๅ“ๅบ”ๅผๅธƒๅฑ€            โ”‚  โ”‚       โ”‚  โ”‚   โ€ข ๅ‚ๆ•ฐ่ฝฌๆข           โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚       โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                 โ”‚       โ”‚              โ”‚                โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚       โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚  โ”‚   ็Šถๆ€ๅฑ‚ (Pinia Store)   โ”‚  โ”‚       โ”‚  โ”‚   ๅฎˆๅซๅฑ‚ (Guards)       โ”‚ โ”‚
โ”‚  โ”‚  โ€ข ๅ…จๅฑ€็Šถๆ€              โ”‚  โ”‚       โ”‚  โ”‚   1. TenantGuard       โ”‚ โ”‚
โ”‚  โ”‚  โ€ข ็”จๆˆทไฟกๆฏ              โ”‚  โ”‚       โ”‚  โ”‚   2. AuthGuard         โ”‚ โ”‚
โ”‚  โ”‚  โ€ข ๆƒ้™ๆ•ฐๆฎ              โ”‚  โ”‚       โ”‚  โ”‚   3. RolesGuard        โ”‚ โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚       โ”‚  โ”‚   4. PermissionGuard   โ”‚ โ”‚
โ”‚                                 โ”‚       โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚       โ”‚              โ”‚                โ”‚
โ”‚  โ”‚   ่ทฏ็”ฑๅฑ‚ (Vue Router)    โ”‚  โ”‚       โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚  โ”‚  โ€ข ๅŠจๆ€่ทฏ็”ฑ              โ”‚  โ”‚       โ”‚  โ”‚  ๆ‹ฆๆˆชๅ™จๅฑ‚ (Interceptors)โ”‚ โ”‚
โ”‚  โ”‚  โ€ข ่ทฏ็”ฑๅฎˆๅซ              โ”‚  โ”‚       โ”‚  โ”‚  1. DecryptInterceptor โ”‚ โ”‚
โ”‚  โ”‚  โ€ข ๆ‡’ๅŠ ่ฝฝ                โ”‚  โ”‚       โ”‚  โ”‚  2. DemoInterceptor    โ”‚ โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚       โ”‚  โ”‚  3. TransformInter...  โ”‚ โ”‚
โ”‚                                 โ”‚       โ”‚  โ”‚  4. LoggingInterceptor โ”‚ โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚       โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚  โ”‚   ่ฏทๆฑ‚ๅฑ‚ (Axios)         โ”‚  โ”‚       โ”‚              โ”‚                โ”‚
โ”‚  โ”‚  โ€ข ่ฏทๆฑ‚ๆ‹ฆๆˆช              โ”‚โ—„โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค                โ”‚
โ”‚  โ”‚  โ€ข ๅ“ๅบ”ๆ‹ฆๆˆช              โ”‚  โ”‚       โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚  โ”‚  โ€ข ้”™่ฏฏๅค„็†              โ”‚  โ”‚       โ”‚  โ”‚   ไธšๅŠกๅฑ‚ (Services)     โ”‚ โ”‚
โ”‚  โ”‚  โ€ข ่ฏทๆฑ‚ๅŠ ๅฏ†              โ”‚  โ”‚       โ”‚  โ”‚  โ€ข ็ณป็ปŸ็ฎก็† Service     โ”‚ โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚       โ”‚  โ”‚  โ€ข ๆƒ้™็ฎก็† Service     โ”‚ โ”‚
โ”‚                                 โ”‚       โ”‚  โ”‚  โ€ข ็›‘ๆŽง็ฎก็† Service     โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜       โ”‚  โ”‚  โ€ข ็งŸๆˆท็ฎก็† Service     โ”‚ โ”‚
                                          โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
                                          โ”‚              โ”‚                โ”‚
                                          โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
                                          โ”‚  โ”‚   ๆ•ฐๆฎ่ฎฟ้—ฎๅฑ‚ (Prisma)   โ”‚ โ”‚
                                          โ”‚  โ”‚  โ€ข Schema ๅฎšไน‰          โ”‚ โ”‚
                                          โ”‚  โ”‚  โ€ข ็ฑปๅž‹็”Ÿๆˆ             โ”‚ โ”‚
                                          โ”‚  โ”‚  โ€ข ๆŸฅ่ฏขๆž„ๅปบๅ™จ           โ”‚ โ”‚
                                          โ”‚  โ”‚  โ€ข ็งŸๆˆทๆ‰ฉๅฑ•             โ”‚ โ”‚
                                          โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
                                          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                                         โ”‚
                        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                        โ”‚                                โ”‚                โ”‚
            โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”‚
            โ”‚  PostgreSQL ๆ•ฐๆฎๅบ“    โ”‚         โ”‚   Redis ็ผ“ๅญ˜        โ”‚     โ”‚
            โ”‚  โ€ข ไธปๆ•ฐๆฎๅญ˜ๅ‚จ         โ”‚         โ”‚   โ€ข ไผš่ฏๅญ˜ๅ‚จ        โ”‚     โ”‚
            โ”‚  โ€ข ไบ‹ๅŠกๆ”ฏๆŒ           โ”‚         โ”‚   โ€ข ๆ•ฐๆฎ็ผ“ๅญ˜        โ”‚     โ”‚
            โ”‚  โ€ข ็ดขๅผ•ไผ˜ๅŒ–           โ”‚         โ”‚   โ€ข ๅˆ†ๅธƒๅผ้”        โ”‚     โ”‚
            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ”‚
                                                                           โ”‚
            โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
            โ”‚                   ๅค–้ƒจๆœๅŠก้›†ๆˆ                            โ”‚  โ”‚
            โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚  โ”‚
            โ”‚  โ”‚  OSS ๅฏน่ฑกๅญ˜ๅ‚จโ”‚  โ”‚  ้‚ฎไปถๆœๅŠก    โ”‚  โ”‚  ็ŸญไฟกๆœๅŠก      โ”‚  โ”‚  โ”‚
            โ”‚  โ”‚  โ€ข ้˜ฟ้‡Œไบ‘    โ”‚  โ”‚  โ€ข SMTP      โ”‚  โ”‚  โ€ข ้˜ฟ้‡Œไบ‘      โ”‚  โ”‚  โ”‚
            โ”‚  โ”‚  โ€ข ไธƒ็‰›ไบ‘    โ”‚  โ”‚  โ€ข SendGrid  โ”‚  โ”‚  โ€ข ่…พ่ฎฏไบ‘      โ”‚  โ”‚  โ”‚
            โ”‚  โ”‚  โ€ข MinIO     โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚  โ”‚
            โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                          โ”‚  โ”‚
            โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚  โ”‚
            โ”‚  โ”‚           ็›‘ๆŽงไธŽๆ—ฅๅฟ—                                  โ”‚โ”‚  โ”‚
            โ”‚  โ”‚  โ€ข Prometheus (ๆŒ‡ๆ ‡้‡‡้›†)                              โ”‚โ”‚  โ”‚
            โ”‚  โ”‚  โ€ข Grafana (ๅฏ่ง†ๅŒ–)                                   โ”‚โ”‚  โ”‚
            โ”‚  โ”‚  โ€ข Pino Logger (ๆ—ฅๅฟ—)                                 โ”‚โ”‚  โ”‚
            โ”‚  โ”‚  โ€ข Terminus (ๅฅๅบทๆฃ€ๆŸฅ)                                โ”‚โ”‚  โ”‚
            โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚  โ”‚
            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
                                                                           โ”‚
                        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                        โ”‚        ้ƒจ็ฝฒ็Žฏๅขƒ (ๅฏ้€‰)
                        โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                        โ”‚  โ”‚      Docker ๅฎนๅ™จๅŒ–            โ”‚
                        โ”‚  โ”‚  โ€ข ๅบ”็”จๅฎนๅ™จ                   โ”‚
                        โ”‚  โ”‚  โ€ข ๆ•ฐๆฎๅบ“ๅฎนๅ™จ                 โ”‚
                        โ”‚  โ”‚  โ€ข Redis ๅฎนๅ™จ                 โ”‚
                        โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                        โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                        โ”‚  โ”‚   Kubernetes ็ผ–ๆŽ’             โ”‚
                        โ”‚  โ”‚  โ€ข Pod ็ฎก็†                   โ”‚
                        โ”‚  โ”‚  โ€ข Service ๆšด้œฒ               โ”‚
                        โ”‚  โ”‚  โ€ข Ingress ่ทฏ็”ฑ               โ”‚
                        โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐Ÿ”„ ๅฎŒๆ•ด่ฏทๆฑ‚ๅค„็†ๆต็จ‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  1. ๅฎขๆˆท็ซฏ   โ”‚  ๅ‘่ตท HTTP ่ฏทๆฑ‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  2. ๅ‰็ซฏ่ฏทๆฑ‚ๆ‹ฆๆˆชๅ™จ                       โ”‚
โ”‚  โ€ข ๆทปๅŠ  Authorization Token              โ”‚
โ”‚  โ€ข ๆทปๅŠ ็งŸๆˆท ID (x-tenant-id)            โ”‚
โ”‚  โ€ข ๆ•ๆ„Ÿๆ•ฐๆฎ AES+RSA ๅŠ ๅฏ†                 โ”‚
โ”‚  โ€ข ๆทปๅŠ ่ฏทๆฑ‚ ID (x-request-id)           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  3. Nginx ็ฝ‘ๅ…ณ                           โ”‚
โ”‚  โ€ข SSL ็ปˆๆญข                              โ”‚
โ”‚  โ€ข ้™ๆ€่ต„ๆบๆœๅŠก                          โ”‚
โ”‚  โ€ข ๅๅ‘ไปฃ็†ๅˆฐๅŽ็ซฏ                        โ”‚
โ”‚  โ€ข ่ฏทๆฑ‚ๆ—ฅๅฟ—่ฎฐๅฝ•                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  4. NestJS ไธญ้—ดไปถๅฑ‚                      โ”‚
โ”‚  โ€ข CORS ๅค„็†                             โ”‚
โ”‚  โ€ข Body ่งฃๆž                             โ”‚
โ”‚  โ€ข Helmet ๅฎ‰ๅ…จๅคด                         โ”‚
โ”‚  โ€ข Request ID ็”Ÿๆˆ                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  5. ๅฎˆๅซๅฑ‚ (Guards) - ๆŒ‰้กบๅบๆ‰ง่กŒ        โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 5.1 TenantGuard                   โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ๆๅ–่ฏทๆฑ‚ๅคดไธญ็š„็งŸๆˆท ID           โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ้ชŒ่ฏ็งŸๆˆทๆ˜ฏๅฆๅญ˜ๅœจไธ”ๆœ‰ๆ•ˆ          โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ่ฎพ็ฝฎ็งŸๆˆทไธŠไธ‹ๆ–‡ (CLS)            โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 5.2 JwtAuthGuard                  โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ้ชŒ่ฏ JWT Token ๆœ‰ๆ•ˆๆ€ง           โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ่งฃๆž็”จๆˆทไฟกๆฏ                    โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ่ฎพ็ฝฎ็”จๆˆทไธŠไธ‹ๆ–‡                  โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ๆฃ€ๆŸฅ Token ๆ˜ฏๅฆ่ฟ‡ๆœŸ             โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 5.3 RolesGuard (ๅฏ้€‰)             โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ๆฃ€ๆŸฅ็”จๆˆท่ง’่‰ฒ                    โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ้ชŒ่ฏๆ˜ฏๅฆๆปก่ถณ่ง’่‰ฒ่ฆๆฑ‚            โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 5.4 PermissionGuard               โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ๆฃ€ๆŸฅ็”จๆˆทๆƒ้™                    โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ้ชŒ่ฏๆ˜ฏๅฆๆœ‰ๆŽฅๅฃ่ฎฟ้—ฎๆƒ้™          โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ๆ”ฏๆŒๆƒ้™็ป„ๅˆ (AND/OR)           โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  6. ๆ‹ฆๆˆชๅ™จๅฑ‚ (Interceptors) - ๅ‰็ฝฎ      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 6.1 DecryptInterceptor            โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ๆฃ€ๆต‹ๅŠ ๅฏ†่ฏทๆฑ‚ๅคด                  โ”‚  โ”‚
โ”‚  โ”‚  โ€ข RSA ่งฃๅฏ† AES ๅฏ†้’ฅ               โ”‚  โ”‚
โ”‚  โ”‚  โ€ข AES ่งฃๅฏ†่ฏทๆฑ‚ไฝ“                  โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ๆ›ฟๆขๅŽŸๅง‹่ฏทๆฑ‚ๆ•ฐๆฎ                โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 6.2 DemoInterceptor               โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ๆฃ€ๆต‹ๆ˜ฏๅฆไธบๆผ”็คบ่ดฆๆˆท              โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ๆ‹ฆๆˆชๅ†™ๆ“ไฝœ (POST/PUT/DELETE)    โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ่ฟ”ๅ›žๅ‹ๅฅฝๆ็คบไฟกๆฏ                โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 6.3 LoggingInterceptor (ๅผ€ๅง‹)     โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ่ฎฐๅฝ•่ฏทๆฑ‚ๅผ€ๅง‹ๆ—ถ้—ด                โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ่ฎฐๅฝ•่ฏทๆฑ‚ๅŸบๆœฌไฟกๆฏ                โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  7. Pipe ็ฎก้“้ชŒ่ฏ                        โ”‚
โ”‚  โ€ข ValidationPipe (DTO ้ชŒ่ฏ)            โ”‚
โ”‚  โ€ข ParseIntPipe (ๅ‚ๆ•ฐ่ฝฌๆข)              โ”‚
โ”‚  โ€ข ่‡ชๅฎšไน‰้ชŒ่ฏ็ฎก้“                        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  8. Controller ๆŽงๅˆถๅ™จ                    โ”‚
โ”‚  โ€ข ๆŽฅๆ”ถ่ฏทๆฑ‚ๅ‚ๆ•ฐ                          โ”‚
โ”‚  โ€ข ่ฐƒ็”จ Service ๆ–นๆณ•                     โ”‚
โ”‚  โ€ข ๅบ”็”จ่ฃ…้ฅฐๅ™จ (@Operlog ็ญ‰)             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  9. Service ไธšๅŠกๅฑ‚                       โ”‚
โ”‚  โ€ข ไธšๅŠก้€ป่พ‘ๅค„็†                          โ”‚
โ”‚  โ€ข ๆ•ฐๆฎ้ชŒ่ฏ                              โ”‚
โ”‚  โ€ข ่ฐƒ็”จ Prisma ๆŸฅ่ฏข                      โ”‚
โ”‚  โ€ข ็ผ“ๅญ˜ๆ“ไฝœ (Redis)                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  10. Prisma ORM ๅฑ‚                       โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 10.1 Tenant Extension             โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ่‡ชๅŠจๆณจๅ…ฅ็งŸๆˆท่ฟ‡ๆปคๆกไปถ            โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ๆ‰€ๆœ‰ๆŸฅ่ฏข่‡ชๅŠจๆทปๅŠ  tenantId       โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ๆ”ฏๆŒ @IgnoreTenant ่ทณ่ฟ‡         โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 10.2 ๆŸฅ่ฏขๆ‰ง่กŒ                     โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ๅ‚ๆ•ฐๅŒ–ๆŸฅ่ฏข (้˜ฒ SQL ๆณจๅ…ฅ)        โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ไบ‹ๅŠกๆ”ฏๆŒ                        โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ๆŸฅ่ฏขไผ˜ๅŒ–                        โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  11. ๆ•ฐๆฎๅบ“ๅฑ‚                            โ”‚
โ”‚  โ€ข PostgreSQL ๆŸฅ่ฏขๆ‰ง่กŒ                   โ”‚
โ”‚  โ€ข ็ดขๅผ•ๆŸฅๆ‰พ                              โ”‚
โ”‚  โ€ข ไบ‹ๅŠกๆไบค/ๅ›žๆปš                         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ (่ฟ”ๅ›žๆ•ฐๆฎ)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  12. ๆ‹ฆๆˆชๅ™จๅฑ‚ (Interceptors) - ๅŽ็ฝฎ      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 12.1 TransformInterceptor         โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ็ปŸไธ€ๅ“ๅบ”ๆ ผๅผ                    โ”‚  โ”‚
โ”‚  โ”‚  โ€ข {code, msg, data, timestamp}    โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ่„ฑๆ•ๅค„็†                        โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 12.2 LoggingInterceptor (็ป“ๆŸ)    โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ่ฎก็ฎ—่ฏทๆฑ‚่€—ๆ—ถ                    โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ่ฎฐๅฝ•ๅ“ๅบ”็Šถๆ€็                   โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ่ฎฐๅฝ•ๅˆฐๆ“ไฝœๆ—ฅๅฟ—่กจ                โ”‚  โ”‚
โ”‚  โ”‚  โ€ข ่พ“ๅ‡บ็ป“ๆž„ๅŒ–ๆ—ฅๅฟ—                  โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  13. ๅผ‚ๅธธ่ฟ‡ๆปคๅ™จ (Exception Filters)      โ”‚
โ”‚  โ€ข ๆ•่Žทๅผ‚ๅธธ                              โ”‚
โ”‚  โ€ข ๆ ผๅผๅŒ–้”™่ฏฏๅ“ๅบ”                        โ”‚
โ”‚  โ€ข ่ฎฐๅฝ•้”™่ฏฏๆ—ฅๅฟ—                          โ”‚
โ”‚  โ€ข ่ฟ”ๅ›žๅ‹ๅฅฝ้”™่ฏฏไฟกๆฏ                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  14. ๅ“ๅบ”่ฟ”ๅ›žๅฎขๆˆท็ซฏ                      โ”‚
โ”‚  โ€ข HTTP Response                         โ”‚
โ”‚  โ€ข ็Šถๆ€็  + ๅ“ๅบ”ไฝ“                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿข ๅคš็งŸๆˆทๆžถๆž„ๅ›พ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     ็งŸๆˆท A ็”จๆˆท                           โ”‚
โ”‚                   (tenantId: 000001)                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     ็งŸๆˆท B ็”จๆˆท                           โ”‚
โ”‚                   (tenantId: 000002)                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
                     โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              NestJS ๅบ”็”จ - TenantGuard                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚  1. ๆๅ–่ฏทๆฑ‚ๅคดไธญ็š„็งŸๆˆท ID (x-tenant-id)            โ”‚  โ”‚
โ”‚  โ”‚  2. ้ชŒ่ฏ็งŸๆˆทๆ˜ฏๅฆๅญ˜ๅœจไธ”็Šถๆ€ไธบๅฏ็”จ                   โ”‚  โ”‚
โ”‚  โ”‚  3. ๅฐ†็งŸๆˆท ID ๅญ˜ๅ…ฅ่ฏทๆฑ‚ไธŠไธ‹ๆ–‡ (CLS)                 โ”‚  โ”‚
โ”‚  โ”‚  4. ๅŽ็ปญๆ‰€ๆœ‰ๆ“ไฝœ่‡ชๅŠจไฝฟ็”จ่ฏฅ็งŸๆˆทไธŠไธ‹ๆ–‡               โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
                     โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚           Prisma Client - Tenant Extension                โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚  ๆ‰€ๆœ‰ๆ•ฐๆฎๅบ“ๆŸฅ่ฏข่‡ชๅŠจๆณจๅ…ฅ็งŸๆˆท่ฟ‡ๆปค:                   โ”‚  โ”‚
โ”‚  โ”‚                                                    โ”‚  โ”‚
โ”‚  โ”‚  ๅŽŸๅง‹ๆŸฅ่ฏข:                                         โ”‚  โ”‚
โ”‚  โ”‚    prisma.sysUser.findMany({ where: { ... } })    โ”‚  โ”‚
โ”‚  โ”‚                                                    โ”‚  โ”‚
โ”‚  โ”‚  ่‡ชๅŠจ่ฝฌๆขไธบ:                                       โ”‚  โ”‚
โ”‚  โ”‚    prisma.sysUser.findMany({                      โ”‚  โ”‚
โ”‚  โ”‚      where: {                                     โ”‚  โ”‚
โ”‚  โ”‚        tenantId: '000001',  // ่‡ชๅŠจๆณจๅ…ฅ           โ”‚  โ”‚
โ”‚  โ”‚        ...                                        โ”‚  โ”‚
โ”‚  โ”‚      }                                            โ”‚  โ”‚
โ”‚  โ”‚    })                                             โ”‚  โ”‚
โ”‚  โ”‚                                                    โ”‚  โ”‚
โ”‚  โ”‚  ๆ”ฏๆŒ็š„ๆ“ไฝœ:                                       โ”‚  โ”‚
โ”‚  โ”‚  โ€ข findMany / findUnique / findFirst              โ”‚  โ”‚
โ”‚  โ”‚  โ€ข create / createMany                            โ”‚  โ”‚
โ”‚  โ”‚  โ€ข update / updateMany                            โ”‚  โ”‚
โ”‚  โ”‚  โ€ข delete / deleteMany                            โ”‚  โ”‚
โ”‚  โ”‚  โ€ข count / aggregate                              โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
                     โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                PostgreSQL ๆ•ฐๆฎๅบ“                          โ”‚
โ”‚                                                           โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚  โ”‚    ็งŸๆˆท A ๆ•ฐๆฎ        โ”‚      โ”‚    ็งŸๆˆท B ๆ•ฐๆฎ        โ”‚ โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚      โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚ โ”‚
โ”‚  โ”‚  โ”‚ tenantId: 0001 โ”‚  โ”‚      โ”‚  โ”‚ tenantId: 0002 โ”‚  โ”‚ โ”‚
โ”‚  โ”‚  โ”‚ user_id: 1     โ”‚  โ”‚      โ”‚  โ”‚ user_id: 100   โ”‚  โ”‚ โ”‚
โ”‚  โ”‚  โ”‚ name: ๅผ ไธ‰     โ”‚  โ”‚      โ”‚  โ”‚ name: ๆŽๅ››     โ”‚  โ”‚ โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚      โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚ โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚      โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚ โ”‚
โ”‚  โ”‚  โ”‚ tenantId: 0001 โ”‚  โ”‚      โ”‚  โ”‚ tenantId: 0002 โ”‚  โ”‚ โ”‚
โ”‚  โ”‚  โ”‚ user_id: 2     โ”‚  โ”‚      โ”‚  โ”‚ user_id: 101   โ”‚  โ”‚ โ”‚
โ”‚  โ”‚  โ”‚ name: ็Ž‹ไบ”     โ”‚  โ”‚      โ”‚  โ”‚ name: ่ตตๅ…ญ     โ”‚  โ”‚ โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚      โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚ โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚                                                           โ”‚
โ”‚  โœ“ ๆ•ฐๆฎๅฎŒๅ…จ้š”็ฆป๏ผŒไบ’ไธๅนฒๆ‰ฐ                                 โ”‚
โ”‚  โœ“ ๅ…ฑไบซๆ•ฐๆฎๅบ“๏ผŒ้™ไฝŽๆˆๆœฌ                                   โ”‚
โ”‚  โœ“ tenantId ๅญ—ๆฎตๅปบ็ซ‹็ดขๅผ•๏ผŒๆŸฅ่ฏข้ซ˜ๆ•ˆ                        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

็‰นๆฎŠๅœบๆ™ฏ: ่ถ…็บง็ฎก็†ๅ‘˜ๆŸฅ่ฏข
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  @IgnoreTenant()  // ่ทณ่ฟ‡็งŸๆˆท่ฟ‡ๆปค                         โ”‚
โ”‚  async getAllTenants() {                                  โ”‚
โ”‚    return await this.prisma.tenant.findMany();           โ”‚
โ”‚  }                                                        โ”‚
โ”‚  // ๅฏไปฅๆŸฅ่ฏขๆ‰€ๆœ‰็งŸๆˆท็š„ๆ•ฐๆฎ                                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ” ๆƒ้™ๆŽงๅˆถๆžถๆž„ๅ›พ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                      ็”จๆˆท็™ปๅฝ•ๆˆๅŠŸ                           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
                     โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚           JWT Token ็”Ÿๆˆ (ๅŒ…ๅซ็”จๆˆทๅŸบๆœฌไฟกๆฏ)                 โ”‚
โ”‚  {                                                          โ”‚
โ”‚    userId: 1,                                               โ”‚
โ”‚    username: 'admin',                                       โ”‚
โ”‚    tenantId: '000000',                                      โ”‚
โ”‚    roles: ['admin'],                                        โ”‚
โ”‚    exp: 1640000000  // ่ฟ‡ๆœŸๆ—ถ้—ด                             โ”‚
โ”‚  }                                                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
                     โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                  ๅ‰็ซฏๅญ˜ๅ‚จ Token                             โ”‚
โ”‚  โ€ข localStorage.setItem('token', token)                     โ”‚
โ”‚  โ€ข ๆฏๆฌก่ฏทๆฑ‚่‡ชๅŠจๆบๅธฆ: Authorization: Bearer ${token}        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
                     โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚            ๅŽ็ซฏๆŽฅๆ”ถ่ฏทๆฑ‚ - ๆƒ้™ๆฃ€ๆŸฅๆต็จ‹                      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ Step 1: JwtAuthGuard                                 โ”‚  โ”‚
โ”‚  โ”‚  โ”œโ”€ ้ชŒ่ฏ Token ็ญพๅ                                  โ”‚  โ”‚
โ”‚  โ”‚  โ”œโ”€ ๆฃ€ๆŸฅ Token ๆ˜ฏๅฆ่ฟ‡ๆœŸ                              โ”‚  โ”‚
โ”‚  โ”‚  โ”œโ”€ ่งฃๆž็”จๆˆทไฟกๆฏ                                     โ”‚  โ”‚
โ”‚  โ”‚  โ””โ”€ ไปŽ Redis ๅŠ ่ฝฝๅฎŒๆ•ด็”จๆˆทๆƒ้™                        โ”‚  โ”‚
โ”‚  โ”‚                                                      โ”‚  โ”‚
โ”‚  โ”‚     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”            โ”‚  โ”‚
โ”‚  โ”‚     โ”‚ Redis ็ผ“ๅญ˜็š„็”จๆˆทๆƒ้™ๆ•ฐๆฎ         โ”‚            โ”‚  โ”‚
โ”‚  โ”‚     โ”‚ user:permissions:1               โ”‚            โ”‚  โ”‚
โ”‚  โ”‚     โ”‚ {                                โ”‚            โ”‚  โ”‚
โ”‚  โ”‚     โ”‚   roles: ['admin', 'user'],     โ”‚            โ”‚  โ”‚
โ”‚  โ”‚     โ”‚   permissions: [                โ”‚            โ”‚  โ”‚
โ”‚  โ”‚     โ”‚     'system:user:add',          โ”‚            โ”‚  โ”‚
โ”‚  โ”‚     โ”‚     'system:user:edit',         โ”‚            โ”‚  โ”‚
โ”‚  โ”‚     โ”‚     'system:user:delete',       โ”‚            โ”‚  โ”‚
โ”‚  โ”‚     โ”‚     'system:user:query',        โ”‚            โ”‚  โ”‚
โ”‚  โ”‚     โ”‚     'system:role:*',  // ้€š้…็ฌฆ โ”‚            โ”‚  โ”‚
โ”‚  โ”‚     โ”‚     ...                         โ”‚            โ”‚  โ”‚
โ”‚  โ”‚     โ”‚   ],                            โ”‚            โ”‚  โ”‚
โ”‚  โ”‚     โ”‚   menuIds: [1,2,3,4,5,...],    โ”‚            โ”‚  โ”‚
โ”‚  โ”‚     โ”‚   dataScope: 'DEPT_AND_CHILD'  โ”‚            โ”‚  โ”‚
โ”‚  โ”‚     โ”‚ }                               โ”‚            โ”‚  โ”‚
โ”‚  โ”‚     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜            โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ Step 2: RolesGuard (ๅฏ้€‰)                            โ”‚  โ”‚
โ”‚  โ”‚  @RequireRole('admin')                               โ”‚  โ”‚
โ”‚  โ”‚  โ”œโ”€ ๆฃ€ๆŸฅ็”จๆˆทๆ˜ฏๅฆๆ‹ฅๆœ‰ๆŒ‡ๅฎš่ง’่‰ฒ                         โ”‚  โ”‚
โ”‚  โ”‚  โ”œโ”€ ๆ”ฏๆŒๅคš่ง’่‰ฒ: @RequireRole('admin', 'manager')    โ”‚  โ”‚
โ”‚  โ”‚  โ””โ”€ ๆ”ฏๆŒ่ง’่‰ฒ็ป„ๅˆ: AND / OR                           โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ Step 3: PermissionGuard                              โ”‚  โ”‚
โ”‚  โ”‚  @RequirePermission('system:user:edit')              โ”‚  โ”‚
โ”‚  โ”‚  โ”œโ”€ ๆๅ–ๆŽฅๅฃๆ‰€้œ€ๆƒ้™                                 โ”‚  โ”‚
โ”‚  โ”‚  โ”œโ”€ ๆฃ€ๆŸฅ็”จๆˆทๆƒ้™ๅˆ—่กจ                                 โ”‚  โ”‚
โ”‚  โ”‚  โ”œโ”€ ๆ”ฏๆŒ้€š้…็ฌฆๅŒน้… (system:user:*)                   โ”‚  โ”‚
โ”‚  โ”‚  โ””โ”€ ๆ”ฏๆŒๆƒ้™็ป„ๅˆ: AND / OR                           โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
         โ”‚ ๆƒ้™้ชŒ่ฏ้€š่ฟ‡           โ”‚ ๆƒ้™้ชŒ่ฏๅคฑ่ดฅ
         โ–ผ                       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ๆ‰ง่กŒไธšๅŠก้€ป่พ‘     โ”‚    โ”‚  ่ฟ”ๅ›ž 403 Forbidden   โ”‚
โ”‚  โ€ข Controller    โ”‚    โ”‚  { code: 403,         โ”‚
โ”‚  โ€ข Service       โ”‚    โ”‚    msg: 'ๆ— ๆƒ้™่ฎฟ้—ฎ' }โ”‚
โ”‚  โ€ข Prisma        โ”‚    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

ๆ•ฐๆฎๆƒ้™ๆŽงๅˆถ (Data Scope):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ๅœจ Service ๅฑ‚ๆ นๆฎ็”จๆˆท็š„ dataScope ่ฟ‡ๆปคๆ•ฐๆฎ:                โ”‚
โ”‚                                                             โ”‚
โ”‚  async findUsers(query, user) {                             โ”‚
โ”‚    const where = { ...query };                              โ”‚
โ”‚                                                             โ”‚
โ”‚    if (user.dataScope === 'ALL') {                          โ”‚
โ”‚      // ๆŸฅ่ฏขๆ‰€ๆœ‰ๆ•ฐๆฎ (่ถ…็บง็ฎก็†ๅ‘˜)                           โ”‚
โ”‚    } else if (user.dataScope === 'DEPT_AND_CHILD') {       โ”‚
โ”‚      // ๆŸฅ่ฏขๆœฌ้ƒจ้—จๅŠๅญ้ƒจ้—จๆ•ฐๆฎ                              โ”‚
โ”‚      where.deptId = { in: user.deptIds };                   โ”‚
โ”‚    } else if (user.dataScope === 'DEPT') {                  โ”‚
โ”‚      // ๅชๆŸฅ่ฏขๆœฌ้ƒจ้—จๆ•ฐๆฎ                                    โ”‚
โ”‚      where.deptId = user.deptId;                            โ”‚
โ”‚    } else if (user.dataScope === 'SELF') {                  โ”‚
โ”‚      // ๅชๆŸฅ่ฏข่‡ชๅทฑ็š„ๆ•ฐๆฎ                                    โ”‚
โ”‚      where.userId = user.userId;                            โ”‚
โ”‚    }                                                         โ”‚
โ”‚                                                             โ”‚
โ”‚    return await this.prisma.user.findMany({ where });       โ”‚
โ”‚  }                                                           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ”’ ่ฏทๆฑ‚ๅŠ ๅฏ†ๆžถๆž„ๅ›พ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    ๅ‰็ซฏ - ๅŠ ๅฏ†ๆต็จ‹                          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 1. ๅ‡†ๅค‡ๆ•ๆ„Ÿๆ•ฐๆฎ                                      โ”‚  โ”‚
โ”‚  โ”‚    const data = {                                    โ”‚  โ”‚
โ”‚  โ”‚      username: 'admin',                              โ”‚  โ”‚
โ”‚  โ”‚      password: 'admin123'  // ๆ•ๆ„Ÿไฟกๆฏ               โ”‚  โ”‚
โ”‚  โ”‚    };                                                โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 2. ็”Ÿๆˆ้šๆœบ AES ๅฏ†้’ฅ (ๆฏๆฌก่ฏทๆฑ‚้ƒฝไธๅŒ)                โ”‚  โ”‚
โ”‚  โ”‚    const aesKey = CryptoJS.lib.WordArray.random(16); โ”‚  โ”‚
โ”‚  โ”‚    // ไพ‹: "a1b2c3d4e5f6g7h8"                         โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 3. ็”จ AES ๅฏ†้’ฅๅŠ ๅฏ†ๆ•ฐๆฎ                                โ”‚  โ”‚
โ”‚  โ”‚    const encryptedData = CryptoJS.AES.encrypt(       โ”‚  โ”‚
โ”‚  โ”‚      JSON.stringify(data),                           โ”‚  โ”‚
โ”‚  โ”‚      aesKey,                                         โ”‚  โ”‚
โ”‚  โ”‚      { mode: CryptoJS.mode.CBC, iv: randomIV }       โ”‚  โ”‚
โ”‚  โ”‚    );                                                โ”‚  โ”‚
โ”‚  โ”‚    // ็ป“ๆžœ: "U2FsdGVkX1+..."  (Base64)               โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 4. ็”จๆœๅŠก็ซฏ RSA ๅ…ฌ้’ฅๅŠ ๅฏ† AES ๅฏ†้’ฅ                     โ”‚  โ”‚
โ”‚  โ”‚    const encryptedKey = RSA.encrypt(                 โ”‚  โ”‚
โ”‚  โ”‚      aesKey.toString(),                              โ”‚  โ”‚
โ”‚  โ”‚      serverPublicKey  // ๆœๅŠก็ซฏๆไพ›็š„ๅ…ฌ้’ฅ            โ”‚  โ”‚
โ”‚  โ”‚    );                                                โ”‚  โ”‚
โ”‚  โ”‚    // RSA 2048 ๅŠ ๅฏ†                                  โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 5. ๅ‘้€ๅŠ ๅฏ†่ฏทๆฑ‚                                      โ”‚  โ”‚
โ”‚  โ”‚    POST /api/login                                   โ”‚  โ”‚
โ”‚  โ”‚    Headers:                                          โ”‚  โ”‚
โ”‚  โ”‚      x-encrypted: true  // ๆ ‡่ฏ†ๅŠ ๅฏ†่ฏทๆฑ‚              โ”‚  โ”‚
โ”‚  โ”‚    Body:                                             โ”‚  โ”‚
โ”‚  โ”‚      {                                               โ”‚  โ”‚
โ”‚  โ”‚        encryptedKey: "MIIBIj...",  // RSA ๅŠ ๅฏ†็š„ๅฏ†้’ฅ โ”‚  โ”‚
โ”‚  โ”‚        encryptedData: "U2FsdG..."  // AES ๅŠ ๅฏ†็š„ๆ•ฐๆฎ โ”‚  โ”‚
โ”‚  โ”‚      }                                               โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
                     โ”‚ HTTPS ๅŠ ๅฏ†ไผ ่พ“
                     โ”‚
                     โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    ๅŽ็ซฏ - ่งฃๅฏ†ๆต็จ‹                          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 1. DecryptInterceptor ๆ‹ฆๆˆช่ฏทๆฑ‚                       โ”‚  โ”‚
โ”‚  โ”‚    if (request.headers['x-encrypted'] === 'true') {  โ”‚  โ”‚
โ”‚  โ”‚      // ๆ‰ง่กŒ่งฃๅฏ†ๆต็จ‹                                 โ”‚  โ”‚
โ”‚  โ”‚    }                                                 โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 2. ็”จๆœๅŠก็ซฏ RSA ็ง้’ฅ่งฃๅฏ† AES ๅฏ†้’ฅ                     โ”‚  โ”‚
โ”‚  โ”‚    const aesKey = RSA.decrypt(                       โ”‚  โ”‚
โ”‚  โ”‚      encryptedKey,                                   โ”‚  โ”‚
โ”‚  โ”‚      serverPrivateKey  // ๆœๅŠก็ซฏ็š„็ง้’ฅ               โ”‚  โ”‚
โ”‚  โ”‚    );                                                โ”‚  โ”‚
โ”‚  โ”‚    // ๅพ—ๅˆฐ: "a1b2c3d4e5f6g7h8"                       โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 3. ็”จ AES ๅฏ†้’ฅ่งฃๅฏ†ๆ•ฐๆฎ                                โ”‚  โ”‚
โ”‚  โ”‚    const decryptedData = AES.decrypt(                โ”‚  โ”‚
โ”‚  โ”‚      encryptedData,                                  โ”‚  โ”‚
โ”‚  โ”‚      aesKey                                          โ”‚  โ”‚
โ”‚  โ”‚    );                                                โ”‚  โ”‚
โ”‚  โ”‚    // ๅพ—ๅˆฐๅŽŸๅง‹ๆ•ฐๆฎ                                   โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 4. ่งฃๆž JSON ๅนถๆ›ฟๆข request.body                      โ”‚  โ”‚
โ”‚  โ”‚    request.body = JSON.parse(decryptedData);         โ”‚  โ”‚
โ”‚  โ”‚    // {                                              โ”‚  โ”‚
โ”‚  โ”‚    //   username: 'admin',                           โ”‚  โ”‚
โ”‚  โ”‚    //   password: 'admin123'                         โ”‚  โ”‚
โ”‚  โ”‚    // }                                              โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 5. ๅŽ็ปญๆต็จ‹ไฝฟ็”จ่งฃๅฏ†ๅŽ็š„ๆ•ฐๆฎ                          โ”‚  โ”‚
โ”‚  โ”‚    Controller ๅ’Œ Service ็›ดๆŽฅไฝฟ็”จ request.body       โ”‚  โ”‚
โ”‚  โ”‚    ๅฎŒๅ…จ้€ๆ˜Ž,ๆ— ้œ€ๅ…ณๅฟƒๅŠ ่งฃๅฏ†้€ป่พ‘                       โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

ๅฎ‰ๅ…จไผ˜ๅŠฟ:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  โœ“ ๅŒ้‡ๅŠ ๅฏ†: AES (ๅฏน็งฐ) + RSA (้žๅฏน็งฐ)                      โ”‚
โ”‚  โœ“ ๆฏๆฌก่ฏทๆฑ‚็š„ AES ๅฏ†้’ฅ้ƒฝไธๅŒ,ๆ— ๆณ•้‡ๆ”พๆ”ปๅ‡ป                   โ”‚
โ”‚  โœ“ RSA ็ง้’ฅๅชๅญ˜ๅœจๆœๅŠก็ซฏ,ๅ‰็ซฏๆ— ๆณ•่งฃๅฏ†                        โ”‚
โ”‚  โœ“ ๅณไฝฟ HTTPS ่ขซ็ ด่งฃ,ๆ•ฐๆฎไป็„ถๅŠ ๅฏ†                           โ”‚
โ”‚  โœ“ ๅฏ†็ ็ญ‰ๆ•ๆ„Ÿไฟกๆฏๆฐธไธๆ˜Žๆ–‡ไผ ่พ“                               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐ŸŽฏ ๆ ธๅฟƒๅŠŸ่ƒฝๆจกๅ—่ฏฆ่งฃ

1๏ธโƒฃ ็ณป็ปŸ็ฎก็†ๆจกๅ—

๐Ÿ‘ค ็”จๆˆท็ฎก็†

ๅฎŒๆ•ด็š„็”จๆˆท็”Ÿๅ‘ฝๅ‘จๆœŸ็ฎก็†๏ผŒๆ”ฏๆŒไผไธš็บง็”จๆˆทไฝ“็ณป๏ผš

  • ็”จๆˆท CRUD๏ผšๆ–ฐๅขžใ€็ผ–่พ‘ใ€ๅˆ ้™คใ€ๆ‰น้‡ๆ“ไฝœ
  • ่ง’่‰ฒๅˆ†้…๏ผšๆ”ฏๆŒไธ€ไธช็”จๆˆทๅคšไธช่ง’่‰ฒ
  • ้ƒจ้—จๅฝ’ๅฑž๏ผšๅ…ณ่”็ป„็ป‡ๆžถๆž„๏ผŒๅฎž็Žฐๆ•ฐๆฎๆƒ้™้š”็ฆป
  • ๅฏ†็ ็ฎก็†๏ผšๅฏ†็ ้‡็ฝฎใ€ๅฏ†็ ๅผบๅบฆ้ชŒ่ฏใ€ๅฎšๆœŸไฟฎๆ”นๆ้†’
  • ็Šถๆ€็ฎก็†๏ผšๅฏ็”จ/็ฆ็”จใ€้”ๅฎš/่งฃ้”
  • ๅฏผๅ…ฅๅฏผๅ‡บ๏ผšๆ‰น้‡ๅฏผๅ…ฅ็”จๆˆทใ€ๅฏผๅ‡บ Excel
  • ็”จๆˆท็”ปๅƒ๏ผš็™ปๅฝ•็ปŸ่ฎกใ€ๆ“ไฝœ่ฎฐๅฝ•ใ€ๆƒ้™่ง†ๅ›พ
๐ŸŽญ ่ง’่‰ฒ็ฎก็†

ๅŸบไบŽ RBAC ็š„็ตๆดปๆƒ้™ๆŽงๅˆถ๏ผš

  • ๆƒ้™ๅˆ†้…๏ผš่œๅ•ๆƒ้™ๆ ‘้€‰ๆ‹ฉ๏ผŒๆ”ฏๆŒๅŠ้€‰็Šถๆ€
  • ๆ•ฐๆฎๆƒ้™๏ผšๅ…จ้ƒจๆ•ฐๆฎ/ๆœฌ้ƒจ้—จ/ๆœฌ้ƒจ้—จๅŠๅญ้ƒจ้—จ/ไป…ๆœฌไบบ
  • ่ง’่‰ฒ็ปงๆ‰ฟ๏ผšๆ”ฏๆŒ่ง’่‰ฒ้—ด็š„ๆƒ้™็ปงๆ‰ฟๅ…ณ็ณป
  • ๅŠจๆ€ๆƒ้™๏ผšๆƒ้™ๅ˜ๆ›ดๅฎžๆ—ถ็”Ÿๆ•ˆ๏ผŒๆ— ้œ€้‡ๆ–ฐ็™ปๅฝ•
  • ๆƒ้™้ข„่งˆ๏ผšๅฏ่ง†ๅŒ–ๅฑ•็คบ่ง’่‰ฒๆ‹ฅๆœ‰็š„ๆ‰€ๆœ‰ๆƒ้™
๐Ÿ“‹ ่œๅ•็ฎก็†

ๅŠจๆ€่œๅ•้…็ฝฎ๏ผŒๆ”ฏๆŒๆ— ้™ๅฑ‚็บง๏ผš

  • ๆ ‘ๅฝข็ป“ๆž„๏ผšๅฏ่ง†ๅŒ–็š„่œๅ•ๆ ‘็ผ–่พ‘
  • ่œๅ•็ฑปๅž‹๏ผš็›ฎๅฝ•ใ€่œๅ•ใ€ๆŒ‰้’ฎไธ‰็ง็ฑปๅž‹
  • ๅ›พๆ ‡้€‰ๆ‹ฉ๏ผšๅ†…็ฝฎๅ›พๆ ‡ๅบ“๏ผŒๆ”ฏๆŒ่‡ชๅฎšไน‰
  • ่ทฏ็”ฑ้…็ฝฎ๏ผšๅ‰็ซฏ่ทฏ็”ฑ่ทฏๅพ„ใ€็ป„ไปถ่ทฏๅพ„
  • ๆƒ้™ๆ ‡่ฏ†๏ผšๆŒ‰้’ฎ็บงๆƒ้™ๆŽงๅˆถๆ ‡่ฏ†
  • ๆ˜พ็คบๆŽงๅˆถ๏ผš่œๅ•ๆ˜พ็คบ/้š่—ใ€็ผ“ๅญ˜ๆŽงๅˆถ
  • ๅค–้“พ่œๅ•๏ผšๆ”ฏๆŒๅค–้ƒจ้“พๆŽฅ่œๅ•
๐Ÿข ้ƒจ้—จ็ฎก็†

ไผไธš็ป„็ป‡ๆžถๆž„็ฎก็†๏ผš

  • ๆ ‘ๅฝข็ป“ๆž„๏ผšๆ— ้™ๅฑ‚็บง็š„้ƒจ้—จๆ ‘
  • ้ƒจ้—จ่ดŸ่ดฃไบบ๏ผš่ฎพ็ฝฎ้ƒจ้—จ่ดŸ่ดฃไบบ
  • ๆŽ’ๅบๆŽงๅˆถ๏ผš่‡ชๅฎšไน‰้ƒจ้—จๆ˜พ็คบ้กบๅบ
  • ๆ•ฐๆฎๆƒ้™๏ผšๅŸบไบŽ้ƒจ้—จ็š„ๆ•ฐๆฎ้š”็ฆป
  • ไบบๅ‘˜็ปŸ่ฎก๏ผš้ƒจ้—จไบบๅ‘˜ๆ•ฐ้‡็ปŸ่ฎก
๐Ÿ’ผ ๅฒ—ไฝ็ฎก็†

ๅฒ—ไฝไฝ“็ณป็ฎก็†๏ผš

  • ๅฒ—ไฝๅฎšไน‰๏ผšๅฒ—ไฝๅ็งฐใ€็ผ–็ ใ€ๆŽ’ๅบ
  • ็Šถๆ€็ฎก็†๏ผšๅฏ็”จ/ๅœ็”จๅฒ—ไฝ
  • ไบบๅ‘˜ๅ…ณ่”๏ผšๆŸฅ็œ‹ๅฒ—ไฝไธ‹็š„ๆ‰€ๆœ‰ไบบๅ‘˜
๐Ÿ“– ๅญ—ๅ…ธ็ฎก็†

็ณป็ปŸๆ•ฐๆฎๅญ—ๅ…ธ็ปŸไธ€็ฎก็†๏ผš

  • ๅญ—ๅ…ธ็ฑปๅž‹๏ผšๅฎšไน‰ๅญ—ๅ…ธๅˆ†็ฑป๏ผˆๅฆ‚๏ผš็”จๆˆท็Šถๆ€ใ€ๆ€งๅˆซ็ญ‰๏ผ‰
  • ๅญ—ๅ…ธๆ•ฐๆฎ๏ผš็ปดๆŠคๅ…ทไฝ“็š„ๅญ—ๅ…ธ้กน
  • ็ผ“ๅญ˜ๆ”ฏๆŒ๏ผšๅญ—ๅ…ธๆ•ฐๆฎ่‡ชๅŠจ็ผ“ๅญ˜๏ผŒๆๅ‡ๆ€ง่ƒฝ
  • ๅ‰็ซฏไฝฟ็”จ๏ผšๅ‰็ซฏ็ปŸไธ€่ฐƒ็”จๅญ—ๅ…ธๆŽฅๅฃ
โš™๏ธ ๅ‚ๆ•ฐ้…็ฝฎ

็ณป็ปŸๅ‚ๆ•ฐๅŠจๆ€้…็ฝฎ๏ผš

  • ้…็ฝฎ้กน็ฎก็†๏ผšๆ–ฐๅขžใ€็ผ–่พ‘ใ€ๅˆ ้™ค้…็ฝฎ้กน
  • ้…็ฝฎๅˆ†็ฑป๏ผš็ณป็ปŸ้…็ฝฎใ€ไธšๅŠก้…็ฝฎ็ญ‰
  • ็ผ“ๅญ˜ๅˆทๆ–ฐ๏ผš้…็ฝฎๅ˜ๆ›ด่‡ชๅŠจๅˆทๆ–ฐ็ผ“ๅญ˜
  • ้…็ฝฎๆ ก้ชŒ๏ผšๆ”ฏๆŒ้…็ฝฎๅ€ผๆ ผๅผ้ชŒ่ฏ
๐Ÿ“ข ้€š็Ÿฅๅ…ฌๅ‘Š

็ณป็ปŸๅ…ฌๅ‘Šๅ‘ๅธƒไธŽ็ฎก็†๏ผš

  • ๅ…ฌๅ‘Šๅ‘ๅธƒ๏ผšๅฏŒๆ–‡ๆœฌ็ผ–่พ‘ๅ™จ๏ผŒๆ”ฏๆŒๅ›พๆ–‡ๆททๆŽ’
  • ๅ…ฌๅ‘Š็ฑปๅž‹๏ผš้€š็Ÿฅใ€ๅ…ฌๅ‘Šใ€็ณป็ปŸๆถˆๆฏ
  • ๅ‘ๅธƒๆŽงๅˆถ๏ผš็ซ‹ๅณๅ‘ๅธƒใ€ๅฎšๆ—ถๅ‘ๅธƒ
  • ้˜…่ฏป็Šถๆ€๏ผšๅทฒ่ฏป/ๆœช่ฏป็Šถๆ€่ทŸ่ธช

2๏ธโƒฃ ็ณป็ปŸ็›‘ๆŽงๆจกๅ—

๐Ÿ‘ฅ ๅœจ็บฟ็”จๆˆท

ๅฎžๆ—ถ็›‘ๆŽงๅœจ็บฟ็”จๆˆท็Šถๆ€๏ผš

  • ๅœจ็บฟๅˆ—่กจ๏ผšๆ˜พ็คบๅฝ“ๅ‰ๆ‰€ๆœ‰ๅœจ็บฟ็”จๆˆท
  • ็”จๆˆทไฟกๆฏ๏ผš็”จๆˆทๅใ€IP ๅœฐๅ€ใ€็™ปๅฝ•ๆ—ถ้—ดใ€ๆต่งˆๅ™จ
  • ๅผบๅˆถไธ‹็บฟ๏ผš็ฎก็†ๅ‘˜ๅฏๅผบๅˆถ็”จๆˆทไธ‹็บฟ
  • ไผš่ฏ็ฎก็†๏ผšๆŸฅ็œ‹็”จๆˆทไผš่ฏไฟกๆฏ
  • ๅฎžๆ—ถ็ปŸ่ฎก๏ผšๅœจ็บฟ็”จๆˆทๆ•ฐ้‡็ปŸ่ฎก
๐Ÿ“ ๆ“ไฝœๆ—ฅๅฟ—

ๅฎŒๆ•ด็š„ๆ“ไฝœๅฎก่ฎก็ณป็ปŸ๏ผš

  • ่‡ชๅŠจ่ฎฐๅฝ•๏ผš้€š่ฟ‡่ฃ…้ฅฐๅ™จ่‡ชๅŠจ่ฎฐๅฝ•ๆ“ไฝœ
  • ่ฏฆ็ป†ไฟกๆฏ๏ผšๆ“ไฝœไบบใ€ๆ“ไฝœๆ—ถ้—ดใ€ๆ“ไฝœ็ฑปๅž‹ใ€่ฏทๆฑ‚ๅ‚ๆ•ฐใ€ๅ“ๅบ”็ป“ๆžœ
  • ๅผ‚ๅธธๆ•่Žท๏ผš่‡ชๅŠจ่ฎฐๅฝ•ๅผ‚ๅธธๆ“ไฝœๅ’Œ้”™่ฏฏๅ †ๆ ˆ
  • ๆกไปถๆŸฅ่ฏข๏ผšๆŒ‰็”จๆˆทใ€ๆ—ถ้—ดใ€ๆจกๅ—ใ€ๆ“ไฝœ็ฑปๅž‹ๆŸฅ่ฏข
  • ๆ•ฐๆฎๅฏผๅ‡บ๏ผšๅฏผๅ‡บๆ—ฅๅฟ—็”จไบŽๅฎก่ฎก
๐Ÿ” ็™ปๅฝ•ๆ—ฅๅฟ—

็™ปๅฝ•ๅฎ‰ๅ…จๅฎก่ฎก๏ผš

  • ็™ปๅฝ•่ฎฐๅฝ•๏ผšๆˆๅŠŸ/ๅคฑ่ดฅ็š„็™ปๅฝ•่ฎฐๅฝ•
  • ๅฎ‰ๅ…จไฟกๆฏ๏ผšIP ๅœฐๅ€ใ€ๅœฐ็†ไฝ็ฝฎใ€ๆต่งˆๅ™จใ€ๆ“ไฝœ็ณป็ปŸ
  • ๅผ‚ๅธธๆฃ€ๆต‹๏ผšๅผ‚ๅธธ็™ปๅฝ•่กŒไธบๆ้†’
  • ็ปŸ่ฎกๅˆ†ๆž๏ผš็™ปๅฝ•ๆ—ถๆฎตๅˆ†ๆžใ€ๅœฐๅŸŸๅˆ†ๆž
โฐ ๅฎšๆ—ถไปปๅŠก

็ตๆดป็š„ไปปๅŠก่ฐƒๅบฆ็ณป็ปŸ๏ผš

  • Cron ่กจ่พพๅผ๏ผšๆ”ฏๆŒๆ ‡ๅ‡† Cron ่กจ่พพๅผ
  • ไปปๅŠก็ฎก็†๏ผšๅฏๅŠจใ€ๅœๆญขใ€็ซ‹ๅณๆ‰ง่กŒ
  • ๆ‰ง่กŒๆ—ฅๅฟ—๏ผšไปปๅŠกๆ‰ง่กŒๅކๅฒใ€ๆˆๅŠŸ/ๅคฑ่ดฅ่ฎฐๅฝ•
  • ๅนถๅ‘ๆŽงๅˆถ๏ผšไปปๅŠกๅนถๅ‘ๆ‰ง่กŒๆŽงๅˆถ
  • ่ถ…ๆ—ถ่ฎพ็ฝฎ๏ผšไปปๅŠกๆ‰ง่กŒ่ถ…ๆ—ถๆ—ถ้—ด่ฎพ็ฝฎ
  • ้”™่ฏฏ้‡่ฏ•๏ผšๅคฑ่ดฅ่‡ชๅŠจ้‡่ฏ•ๆœบๅˆถ
๐Ÿ–ฅ๏ธ ็ณป็ปŸ็›‘ๆŽง

ๆœๅŠกๅ™จ่ฟ่กŒ็Šถๆ€็›‘ๆŽง๏ผš

  • CPU ็›‘ๆŽง๏ผšCPU ไฝฟ็”จ็އใ€ๆ ธๅฟƒๆ•ฐ
  • ๅ†…ๅญ˜็›‘ๆŽง๏ผšๅ†…ๅญ˜ไฝฟ็”จๆƒ…ๅ†ตใ€JVM ไฟกๆฏ
  • ็ฃ็›˜็›‘ๆŽง๏ผš็ฃ็›˜ไฝฟ็”จ็އใ€่ฏปๅ†™้€Ÿๅบฆ
  • ็ฝ‘็ปœ็›‘ๆŽง๏ผš็ฝ‘็ปœๆต้‡็ปŸ่ฎก
  • ่ฟ›็จ‹ไฟกๆฏ๏ผš่ฟ›็จ‹ PIDใ€่ฟ่กŒๆ—ถ้•ฟ
๐Ÿ’š ๅฅๅบทๆฃ€ๆŸฅ

K8s ๅ‹ๅฅฝ็š„ๅฅๅบทๆฃ€ๆŸฅ๏ผš

  • Liveness ๆŽข้’ˆ๏ผšๅบ”็”จๅญ˜ๆดปๆฃ€ๆŸฅ
  • Readiness ๆŽข้’ˆ๏ผšๅบ”็”จๅฐฑ็ปชๆฃ€ๆŸฅ
  • ๆ•ฐๆฎๅบ“ๆฃ€ๆŸฅ๏ผšPostgreSQL ่ฟžๆŽฅ็Šถๆ€
  • Redis ๆฃ€ๆŸฅ๏ผšRedis ่ฟžๆŽฅ็Šถๆ€
  • ็ฃ็›˜ๆฃ€ๆŸฅ๏ผš็ฃ็›˜็ฉบ้—ดๆฃ€ๆŸฅ
  • ๅ†…ๅญ˜ๆฃ€ๆŸฅ๏ผšๅ†…ๅญ˜ไฝฟ็”จๆฃ€ๆŸฅ
  • Prometheus ๆŒ‡ๆ ‡๏ผšๆšด้œฒ /api/metrics ็ซฏ็‚น
๐Ÿ“ ๆ–‡ไปถไธŠไผ 

ๅคšๅญ˜ๅ‚จๆ”ฏๆŒ็š„ๆ–‡ไปถ็ฎก็†๏ผš

  • ๆœฌๅœฐๅญ˜ๅ‚จ๏ผšๅญ˜ๅ‚จๅˆฐๆœๅŠกๅ™จๆœฌๅœฐ
  • ้˜ฟ้‡Œไบ‘ OSS๏ผšๅญ˜ๅ‚จๅˆฐ้˜ฟ้‡Œไบ‘ๅฏน่ฑกๅญ˜ๅ‚จ
  • ไธƒ็‰›ไบ‘๏ผšๅญ˜ๅ‚จๅˆฐไธƒ็‰›ไบ‘
  • MinIO๏ผš็งๆœ‰ๅŒ–ๅฏน่ฑกๅญ˜ๅ‚จ
  • ๆ–‡ไปถ้ข„่งˆ๏ผšๅ›พ็‰‡ใ€PDF ็ญ‰ๅœจ็บฟ้ข„่งˆ
  • ็ผฉ็•ฅๅ›พ๏ผš่‡ชๅŠจ็”Ÿๆˆๅ›พ็‰‡็ผฉ็•ฅๅ›พ

3๏ธโƒฃ ๅคš็งŸๆˆท็ฎก็†ๆจกๅ—

๐Ÿ˜๏ธ ็งŸๆˆท็ฎก็†

ๅฎŒๆ•ด็š„ SaaS ็งŸๆˆท็ฎก็†๏ผš

  • ็งŸๆˆท CRUD๏ผšๆ–ฐๅขžใ€็ผ–่พ‘ใ€ๅˆ ้™ค็งŸๆˆท
  • ็งŸๆˆทไฟกๆฏ๏ผš็งŸๆˆทๅ็งฐใ€่”็ณปไบบใ€ๅˆฐๆœŸๆ—ถ้—ด
  • ๅฅ—้ค็ป‘ๅฎš๏ผšไธบ็งŸๆˆทๅˆ†้…ๅŠŸ่ƒฝๅฅ—้ค
  • ็Šถๆ€็ฎก็†๏ผšๅฏ็”จใ€ๅœ็”จใ€่ฟ‡ๆœŸๆŽงๅˆถ
  • ๆ•ฐๆฎ้š”็ฆป๏ผš่‡ชๅŠจ็š„็งŸๆˆทๆ•ฐๆฎ้š”็ฆป
  • ๅฎน้‡้™ๅˆถ๏ผš็”จๆˆทๆ•ฐใ€ๅญ˜ๅ‚จ็ฉบ้—ด้™ๅˆถ
๐Ÿ“ฆ ็งŸๆˆทๅฅ—้ค

็ตๆดป็š„ๅฅ—้คไฝ“็ณป๏ผš

  • ๅฅ—้คๅฎšไน‰๏ผšๅŸบ็ก€็‰ˆใ€ๆ ‡ๅ‡†็‰ˆใ€ไผไธš็‰ˆ
  • ๅŠŸ่ƒฝๆƒ้™๏ผš่œๅ•ๆƒ้™ๆŒ‰ๅฅ—้คๅˆ†้…
  • ่ต„ๆบ้™ๅˆถ๏ผš็”จๆˆทๆ•ฐใ€ๅญ˜ๅ‚จ็ฉบ้—ด้™ๅˆถ
  • ๅฅ—้คๅ‡็บง๏ผšๆ”ฏๆŒๅฅ—้คๅœจ็บฟๅ‡็บง
๐Ÿ”’ ๆ•ฐๆฎ้š”็ฆป

ๅฎ‰ๅ…จ็š„ๅคš็งŸๆˆทๆ•ฐๆฎ้š”็ฆป๏ผš

  • ่‡ชๅŠจ่ฟ‡ๆปค๏ผšๆ•ฐๆฎๅบ“ๆŸฅ่ฏข่‡ชๅŠจๆŒ‰็งŸๆˆท่ฟ‡ๆปค
  • ่ทจ็งŸๆˆทๆŸฅ่ฏข๏ผš่ถ…็บง็ฎก็†ๅ‘˜ๅฏๆŸฅ่ฏขๆ‰€ๆœ‰็งŸๆˆท
  • ็งŸๆˆทๅˆ‡ๆข๏ผšๆ”ฏๆŒๅˆ‡ๆขๆŸฅ็œ‹ไธๅŒ็งŸๆˆทๆ•ฐๆฎ
  • ๆ•ฐๆฎ่ฟ็งป๏ผš็งŸๆˆทๆ•ฐๆฎๅฏผๅ…ฅๅฏผๅ‡บ

4๏ธโƒฃ ๆผ”็คบ่ดฆๆˆท็ณป็ปŸ

ไธ“ไธบไบงๅ“ๆผ”็คบ่ฎพ่ฎก็š„ๅฎ‰ๅ…จๆœบๅˆถ๏ผš

  • ๅช่ฏปๆƒ้™๏ผš52 ไธชๆŸฅ่ฏขๆƒ้™๏ผŒๅฏๆŸฅ็œ‹ๆ‰€ๆœ‰ๆจกๅ—
  • ๅ†™ๆ“ไฝœๆ‹ฆๆˆช๏ผš่‡ชๅŠจๆ‹ฆๆˆชๆ‰€ๆœ‰ POST/PUT/DELETE/PATCH ่ฏทๆฑ‚
  • ๅ‹ๅฅฝๆ็คบ๏ผšๆ“ไฝœ่ขซๆ‹ฆๆˆชๆ—ถ็ป™ๅ‡บๅ‹ๅฅฝๆ็คบ
  • ็ตๆดป้…็ฝฎ๏ผšๅŸบไบŽ RBAC ๅฏ้šๆ—ถ่ฐƒๆ•ดๆƒ้™่Œƒๅ›ด
  • ๆผ”็คบ้‡็ฝฎ๏ผšๅฎšๆ—ถ้‡็ฝฎๆผ”็คบๆ•ฐๆฎ๏ผˆๅฏ้€‰๏ผ‰

๐Ÿš€ ๅฟซ้€Ÿๅผ€ๅง‹

็Žฏๅขƒ่ฆๆฑ‚

  • Node.js >= 20.19.0
  • PostgreSQL >= 14
  • Redis >= 7
  • pnpm >= 8.0

ๅฎ‰่ฃ…ๆญฅ้ชค

1. ๅ…‹้š†้กน็›ฎ

git clone https://github.com/your-repo/nest-admin-soybean.git
cd nest-admin-soybean

2. ๅŽ็ซฏ้…็ฝฎ

cd server
pnpm install

# ็”Ÿๆˆ RSA ๅฏ†้’ฅๅฏน๏ผˆ็”จไบŽๅŠ ๅฏ†๏ผ‰
pnpm generate:keys

# ้…็ฝฎๆ•ฐๆฎๅบ“่ฟžๆŽฅ
# ็ผ–่พ‘ src/config/index.ts ไธญ็š„ๆ•ฐๆฎๅบ“้…็ฝฎ

# ๅˆๅง‹ๅŒ–ๆ•ฐๆฎๅบ“
pnpm prisma:seed

3. ๅ‰็ซฏ้…็ฝฎ

cd admin-naive-ui
pnpm install

# ้…็ฝฎๅŽ็ซฏๆŽฅๅฃๅœฐๅ€
# ็ผ–่พ‘ .env.development ๆ–‡ไปถ

4. ๅฏๅŠจ้กน็›ฎ

# ๅฏๅŠจๅŽ็ซฏ (8080็ซฏๅฃ)
cd server
pnpm start:dev

# ๅฏๅŠจๅ‰็ซฏ (9527็ซฏๅฃ)
cd admin-naive-ui
pnpm dev

5. ่ฎฟ้—ฎ็ณป็ปŸ

้ป˜่ฎค่ดฆๅท๏ผš

  • ่ถ…็บง็ฎก็†ๅ‘˜๏ผšadmin / admin123 (็งŸๆˆท 000000)
  • ๆผ”็คบ่ดฆๆˆท๏ผšdemo / demo123 (็งŸๆˆท 000000)

๐Ÿ’ช ๆŠ€ๆœฏไบฎ็‚น่ฏฆ่งฃ

1. ๅคš็งŸๆˆทๅฎž็ŽฐๅŽŸ็†

ๆ ธๅฟƒๆ€่ทฏ๏ผš้€š่ฟ‡ Prisma Extension ๅœจ ORM ๅฑ‚้ขๅฎž็Žฐ้€ๆ˜Ž็š„็งŸๆˆท่ฟ‡ๆปคใ€‚

// tenant.extension.ts
export function tenantExtension(tenantId: string) {
  return Prisma.defineExtension({
    query: {
      // ๅฏนๆ‰€ๆœ‰ๆจกๅž‹็š„ๆŸฅ่ฏข่‡ชๅŠจๆทปๅŠ ็งŸๆˆท่ฟ‡ๆปค
      $allModels: {
        async findMany({ args, query }) {
          args.where = { ...args.where, tenantId };
          return query(args);
        },
        // ... findUnique, create, update, delete ๅŒ็†
      }
    }
  });
}

// prisma.service.ts
get client() {
  const tenantId = TenantContext.getTenantId();
  if (!tenantId) return this._client;
  return this._client.$extends(tenantExtension(tenantId));
}

ไผ˜ๅŠฟ๏ผš

  • ไธšๅŠกไปฃ็ ๆ— ้œ€ๅ…ณๅฟƒ็งŸๆˆท้€ป่พ‘
  • ้ฟๅ…ๅฟ˜่ฎฐๆทปๅŠ ็งŸๆˆทๆกไปถๅฏผ่‡ด็š„ๆ•ฐๆฎๆณ„้œฒ
  • ็ปŸไธ€็ฎก็†๏ผŒๆ˜“ไบŽ็ปดๆŠค

2. ่ฏทๆฑ‚ๅŠ ๅฏ†็š„ๅฎž็Žฐ

ๅ‰็ซฏๅŠ ๅฏ†๏ผš

// encryption.ts
export function encryptRequest(data: any) {
  // 1. ็”Ÿๆˆ้šๆœบ AES ๅฏ†้’ฅ
  const aesKey = CryptoJS.lib.WordArray.random(16);
  
  // 2. AES ๅŠ ๅฏ†ๆ•ฐๆฎ
  const encryptedData = CryptoJS.AES.encrypt(
    JSON.stringify(data), 
    aesKey,
    { mode: CryptoJS.mode.CBC }
  ).toString();
  
  // 3. RSA ๅŠ ๅฏ† AES ๅฏ†้’ฅ
  const encryptedKey = rsaEncrypt(aesKey.toString(), serverPublicKey);
  
  return { encryptedKey, encryptedData };
}

ๅŽ็ซฏ่งฃๅฏ†๏ผš

// decrypt.interceptor.ts
@Injectable()
export class DecryptInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler) {
    const request = context.switchToHttp().getRequest();
    
    if (request.headers['x-encrypted'] === 'true') {
      const { encryptedKey, encryptedData } = request.body;
      
      // 1. RSA ่งฃๅฏ† AES ๅฏ†้’ฅ
      const aesKey = rsaDecrypt(encryptedKey, privateKey);
      
      // 2. AES ่งฃๅฏ†ๆ•ฐๆฎ
      const decryptedData = aesDecrypt(encryptedData, aesKey);
      
      // 3. ๆ›ฟๆข body
      request.body = JSON.parse(decryptedData);
    }
    
    return next.handle();
  }
}

3. ๆƒ้™็ณป็ปŸ็š„่ฎพ่ฎก

้‡‡็”จ่ฃ…้ฅฐๅ™จ + ๅฎˆๅซ็š„็ป„ๅˆๆจกๅผ๏ผš

// permission.guard.ts
@Injectable()
export class PermissionGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const requiredPermission = this.reflector.get(
      'permission',
      context.getHandler()
    );
    
    if (!requiredPermission) return true;
    
    const user = context.switchToHttp().getRequest().user;
    return user.permissions.includes(requiredPermission);
  }
}

// ไฝฟ็”จ
@RequirePermission('system:user:edit')
@Put(':id')
updateUser(@Param('id') id: string, @Body() dto: UpdateUserDto) {
  return this.userService.update(id, dto);
}

ๆƒ้™ๆ•ฐๆฎ็ป“ๆž„๏ผš

// ๆƒ้™ๆ ‡่ฏ†๏ผšๆจกๅ—:ๅŠŸ่ƒฝ:ๆ“ไฝœ
'system:user:add'      // ๆทปๅŠ ็”จๆˆท
'system:user:edit'     // ็ผ–่พ‘็”จๆˆท
'system:user:delete'   // ๅˆ ้™ค็”จๆˆท
'system:user:query'    // ๆŸฅ่ฏข็”จๆˆท

4. ๆ—ฅๅฟ—็ณป็ปŸ็š„ไผ˜ๅŒ–

่‡ชๅŠจๆ—ฅๅฟ—ๆ”ถ้›†๏ผš

// logging.interceptor.ts
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  constructor(private logger: Logger) {}
  
  intercept(context: ExecutionContext, next: CallHandler) {
    const request = context.switchToHttp().getRequest();
    const startTime = Date.now();
    
    return next.handle().pipe(
      tap(() => {
        const duration = Date.now() - startTime;
        this.logger.info({
          requestId: request.id,
          tenantId: request.tenantId,
          username: request.user?.username,
          method: request.method,
          url: request.url,
          statusCode: context.switchToHttp().getResponse().statusCode,
          duration,
        });
      })
    );
  }
}

ๆ•ๆ„Ÿๅญ—ๆฎต่„ฑๆ•๏ผš

// ่‡ชๅŠจ้š่—ๆ•ๆ„Ÿๅญ—ๆฎต
const sensitiveFields = ['password', 'token', 'secret'];
this.logger.info(redactSensitive(data, sensitiveFields));

๐ŸŽจ ๅ‰็ซฏ็‰น่‰ฒ

1. ๆ–‡ไปถ่ทฏ็”ฑ็ณป็ปŸ

ไฝฟ็”จ @elegant-router/vue ๅฎž็ŽฐๅŸบไบŽๆ–‡ไปถ็š„่ทฏ็”ฑ๏ผš

src/views/
โ”œโ”€โ”€ system/
โ”‚   โ”œโ”€โ”€ user/
โ”‚   โ”‚   โ””โ”€โ”€ index.vue     โ†’ /system/user
โ”‚   โ”œโ”€โ”€ role/
โ”‚   โ”‚   โ””โ”€โ”€ index.vue     โ†’ /system/role
โ”‚   โ””โ”€โ”€ menu/
โ”‚       โ””โ”€โ”€ index.vue     โ†’ /system/menu

่‡ชๅŠจ็”Ÿๆˆ่ทฏ็”ฑ๏ผš

pnpm gen-route

2. ๅŽŸๅญๅŒ– CSS

ไฝฟ็”จ UnoCSS๏ผŒๆ”ฏๆŒ Tailwind ้ฃŽๆ ผ๏ผš

<template>
  <div class="flex items-center justify-between p-4 bg-white dark:bg-dark">
    <span class="text-lg font-bold">็”จๆˆท็ฎก็†</span>
    <n-button type="primary">ๆทปๅŠ ็”จๆˆท</n-button>
  </div>
</template>

3. ็Šถๆ€็ฎก็†

Pinia setup ่ฏญๆณ•๏ผš

// useAuthStore.ts
export const useAuthStore = defineStore('auth', () => {
  const token = ref(getToken());
  const userInfo = ref<UserInfo | null>(null);
  
  async function login(credentials: LoginDto) {
    const { token: newToken, user } = await api.login(credentials);
    token.value = newToken;
    userInfo.value = user;
    setToken(newToken);
  }
  
  return { token, userInfo, login };
});

4. ่ฏทๆฑ‚ๅฐ่ฃ…

็ปŸไธ€็š„ Axios ๅฐ่ฃ…๏ผŒๆ”ฏๆŒ่‡ชๅŠจๅŠ ๅฏ†๏ผš

// api.ts
export function fetchUserList(params: UserQueryDto) {
  return request<PageResult<User>>({
    url: '/system/user',
    method: 'GET',
    params
  });
}

export function createUser(data: CreateUserDto) {
  return request({
    url: '/system/user',
    method: 'POST',
    data,
    encrypt: true  // ่‡ชๅŠจๅŠ ๅฏ†
  });
}

๐Ÿ”ฎ ๆœชๆฅ่ง„ๅˆ’

  • ๅพฎๆœๅŠกๆžถๆž„๏ผšๆ‹†ๅˆ†ไธบ็‹ฌ็ซ‹็š„ๅพฎๆœๅŠก
  • ๆถˆๆฏ้˜Ÿๅˆ—๏ผš้›†ๆˆ RabbitMQ/Kafka
  • ๅˆ†ๅธƒๅผ่ฟฝ่ธช๏ผšๆŽฅๅ…ฅ OpenTelemetry
  • GraphQL API๏ผšๆไพ› GraphQL ๆŽฅๅฃ
  • ็งปๅŠจ็ซฏ๏ผšๅผ€ๅ‘้…ๅฅ—็š„็งปๅŠจๅบ”็”จ
  • ไฝŽไปฃ็ ๅนณๅฐ๏ผšๅฏ่ง†ๅŒ–่กจๅ•่ฎพ่ฎกๅ™จ
  • ๆ›ดๅคšๆ•ฐๆฎๅบ“๏ผšๆ”ฏๆŒ MySQLใ€MongoDB
  • ไบ‘ๅŽŸ็”Ÿ๏ผšK8s Helm Chart

ๅฆ‚ๆžœ่ง‰ๅพ—ไธ้”™๏ผŒๆฌข่ฟŽ Star โญ๏ธ๏ผšgithub.com/linlingqin7โ€ฆ

#NestJS #Vue3 #ๅŽๅฐ็ฎก็†็ณป็ปŸ #ๅ…จๆ ˆๅผ€ๅ‘ #ๅผ€ๆบ้กน็›ฎ