Files
vexplor/docs/NodeJS_Refactoring_Rules.md
2025-08-25 14:08:08 +09:00

50 KiB

Java Spring Boot → Node.js + TypeScript 리팩토링 가이드라인

📋 프로젝트 개요

목표

  • 기존 Java Spring Boot 백엔드를 Node.js + TypeScript로 완전 리팩토링
  • React 프론트엔드와의 완벽한 통합
  • 타입 안전성과 개발 생산성 향상

기술 스택

{
  "runtime": "Node.js ^20.10.0",
  "framework": "Express ^4.18.2",
  "language": "TypeScript ^5.3.3",
  "orm": "Prisma ^5.7.1",
  "database": "PostgreSQL ^8.11.3",
  "authentication": "JWT + Passport",
  "testing": "Jest + Supertest"
}

🏗️ 아키텍처 원칙

1. 계층별 분리

Controller → Service → Repository → Database
    ↓           ↓          ↓          ↓
  라우팅    비즈니스로직   데이터접근   PostgreSQL

2. 의존성 주입 패턴

interface IUserService {
  getUsers(): Promise<User[]>;
  createUser(user: CreateUserDto): Promise<User>;
}

class UserController {
  constructor(private userService: IUserService) {}
}

3. 타입 안전성 우선

  • 모든 API 응답/요청에 TypeScript 인터페이스 정의
  • Prisma 스키마 기반 타입 자동 생성
  • 런타임 타입 검증 (Joi)

📁 프로젝트 구조 규칙

디렉토리 구조

src/
├── config/          # 설정 파일
├── controllers/     # HTTP 요청 처리
├── services/        # 비즈니스 로직
├── repositories/    # 데이터 접근 계층
├── middleware/      # Express 미들웨어
├── utils/           # 유틸리티 함수
├── types/           # TypeScript 타입 정의
├── validators/      # 입력 검증 스키마
└── app.ts          # 애플리케이션 진입점

파일 명명 규칙

  • 컨트롤러: {Domain}Controller.ts (예: UserController.ts)
  • 서비스: {Domain}Service.ts (예: UserService.ts)
  • 리포지토리: {Domain}Repository.ts (예: UserRepository.ts)
  • 타입: {Domain}.types.ts (예: user.types.ts)
  • 검증: {Domain}.validator.ts (예: user.validator.ts)

💻 코딩 컨벤션

1. TypeScript 규칙

// ✅ 권장
interface CreateUserRequest {
  userName: string;
  email: string;
  password: string;
  deptCode?: string;
}

type UserResponse = {
  id: number;
  userName: string;
  email: string;
  status: "Y" | "N";
  regDate: Date;
};

// ❌ 금지
const user: any = {};
const userName: string = req.body.userName as string;

2. 클래스 및 함수 정의

// ✅ 권장
export class UserService {
  constructor(private userRepository: UserRepository) {}

  async getUsers(params: GetUsersParams): Promise<UserResponse[]> {
    try {
      return await this.userRepository.findMany(params);
    } catch (error) {
      throw new ServiceError("사용자 목록 조회 실패", error);
    }
  }
}

3. 에러 처리

export class ServiceError extends Error {
  constructor(
    message: string,
    public originalError?: Error,
    public statusCode: number = 500
  ) {
    super(message);
    this.name = "ServiceError";
  }
}

🔐 인증 시스템 마이그레이션 가이드라인

1. 기존 인증 시스템 분석

현재 Java Spring Boot 인증 구조

// 기존 API 엔드포인트
POST /api/auth/login      // 로그인
POST /api/auth/logout     // 로그아웃
GET  /api/auth/me         // 현재 사용자 정보
GET  /api/auth/status     // 인증 상태 확인

// 기존 핵심 클래스
- ApiLoginController: REST API 기반 로그인 컨트롤러
- LoginService: 로그인 비즈니스 로직
- PersonBean: 사용자 정보 객체
- EncryptUtil: 비밀번호 암호화 유틸리티

기존 인증 플로우

  1. 로그인 검증: LoginService.loginPwdCheck() - 사용자 ID/비밀번호 검증
  2. 세션 관리: SessionManager - HttpSession 기반 세션 관리
  3. 로그 기록: insertLoginAccessLog() - LOGIN_ACCESS_LOG 테이블에 접속 로그
  4. 사용자 정보: PersonBean - 세션에 저장되는 사용자 정보 객체

2. Node.js 마이그레이션 전략

기존 로직 유지 원칙

  • 기존 비즈니스 로직 그대로 유지
  • 기존 데이터베이스 구조 그대로 사용
  • 기존 API 응답 형식 유지
  • 기존 로그인 플로우 유지
  • 🔄 세션 → JWT 토큰으로 변경 (기능은 동일)

변경 사항

// 기존: HttpSession 기반
HttpSession session = request.getSession();
session.setAttribute(Constants.PERSON_BEAN, person);

// 변경: JWT 토큰 기반
const token = jwt.sign({ userId: user.userId, ...userInfo }, secret);

3. 인증 시스템 구현 계획

Phase 2-1A: 기본 인증 구조 (1주)

3.1 타입 정의 (기존 구조 유지)

// src/types/auth.ts
export interface LoginRequest {
  userId: string;
  password: string;
}

export interface UserInfo {
  userId: string;
  userName: string;
  deptName: string;
  companyCode: string;
  companyName: string;
}

export interface ApiResponse<T = any> {
  success: boolean;
  message?: string;
  data?: T;
  error?: {
    code: string;
    details?: any;
  };
}

3.2 비밀번호 암호화 (기존 로직 포팅)

// src/utils/passwordUtils.ts
// 기존 EncryptUtil.encrypt() 로직을 Node.js로 포팅
export class PasswordUtils {
  static encrypt(password: string): string {
    // 기존 Java EncryptUtil 로직을 그대로 포팅
  }

  static matches(plainPassword: string, encryptedPassword: string): boolean {
    // 기존 Java EncryptUtil.matches() 로직 포팅
  }
}

3.3 JWT 토큰 관리

// src/utils/jwtUtils.ts
export class JwtUtils {
  static generateToken(userInfo: UserInfo): string {
    // PersonBean 정보를 JWT 페이로드로 변환
  }

  static verifyToken(token: string): UserInfo {
    // JWT 토큰 검증 및 사용자 정보 추출
  }
}

3.4 인증 미들웨어

// src/middleware/authMiddleware.ts
export const authenticateToken = (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  // JWT 토큰 검증하여 기존 세션 방식과 동일한 효과
  // req.user에 사용자 정보 설정 (기존 PersonBean과 동일)
};

Phase 2-1B: 핵심 인증 API (1주)

3.5 로그인 API (POST /api/auth/login)

// src/controllers/authController.ts
export class AuthController {
  async login(req: Request, res: Response) {
    // 기존 ApiLoginController.login() 로직을 그대로 포팅
    // 1. 사용자 ID/비밀번호 검증
    // 2. 기존 loginPwdCheck 로직 사용
    // 3. 로그인 로그 기록
    // 4. JWT 토큰 발급 (세션 대체)
  }
}

3.6 인증 서비스

// src/services/authService.ts
export class AuthService {
  async loginPwdCheck(userId: string, password: string): Promise<LoginResult> {
    // 기존 LoginService.loginPwdCheck() 로직 포팅
  }

  async insertLoginAccessLog(logData: LoginLogData): Promise<void> {
    // 기존 insertLoginAccessLog() 로직 포팅
  }
}

4. 파일 구조 (기존 로직 유지)

backend-node/src/
├── auth/
│   ├── authController.ts      # ApiLoginController 포팅
│   ├── authService.ts         # LoginService 포팅
│   └── authMiddleware.ts      # JWT 기반 인증
├── types/
│   ├── auth.ts               # 기존 DTO 클래스 포팅
│   └── common.ts             # 기존 ApiResponse 포팅
├── utils/
│   ├── passwordUtils.ts      # EncryptUtil 포팅
│   ├── jwtUtils.ts           # JWT 토큰 관리
│   └── logger.ts             # 기존 로깅 로직
└── routes/
    └── authRoutes.ts         # 기존 API 엔드포인트

5. 기존 로직 포팅 우선순위

우선순위 1: 핵심 인증 로직

  1. 비밀번호 암호화 유틸리티 (EncryptUtil 포팅)
  2. 로그인 검증 로직 (LoginService.loginPwdCheck 포팅)
  3. 로그인 API (ApiLoginController.login 포팅)

우선순위 2: 보조 기능

  1. 로그인 로그 기록 (insertLoginAccessLog 포팅)
  2. 사용자 정보 조회 (getCurrentUser 포팅)
  3. 로그아웃 API (logout 포팅)

6. 테스트 전략

기존 API 호환성 테스트

  • 기존 Java API와 동일한 응답 형식 확인
  • 기존 로그인 플로우 동작 확인
  • 기존 로그 기록 기능 확인

보안 테스트

  • JWT 토큰 유효성 검증
  • 비밀번호 암호화 정확성 확인
  • Rate Limiting 동작 확인

🗄️ 데이터베이스 설계 규칙

1. 기존 데이터베이스 스키마 참고

참고 문서: docs/Database_Schema_Collection.md

이 문서에는 기존 PostgreSQL 데이터베이스의 완전한 스키마 정보가 포함되어 있습니다:

  • 전체 테이블 목록 (약 200개 테이블)
  • 각 테이블의 상세 컬럼 구조
  • 외래키 관계 정보
  • 인덱스 및 제약조건 정보
  • 시퀀스, 뷰, 함수 정보

2. 핵심 테이블 구조

user_info 테이블

model UserInfo {
  sabun           String?  @map("sabun")                    // 사번
  userId          String   @unique @map("user_id")          // 사용자 ID (NOT NULL)
  userPassword    String?  @map("user_password")            // 비밀번호
  userName        String?  @map("user_name")                // 사용자명
  userNameEng     String?  @map("user_name_eng")            // 영문명
  userNameCn      String?  @map("user_name_cn")             // 중문명
  deptCode        String?  @map("dept_code")                // 부서코드
  deptName        String?  @map("dept_name")                // 부서명
  positionCode    String?  @map("position_code")            // 직급코드
  positionName    String?  @map("position_name")            // 직급명

  // 관계 정의
  userAuths       UserAuth[]
  deptInfo        DeptInfo? @relation(fields: [deptCode], references: [deptCode])

  @@map("user_info")
}

menu_info 테이블

model MenuInfo {
  objid           Int      @id @default(autoincrement()) @map("objid")           // 객체ID (NOT NULL)
  menuType        Int?     @map("menu_type")                                     // 메뉴타입
  parentObjId     Int?     @map("parent_obj_id")                                 // 부모객체ID
  menuNameKor     String?  @map("menu_name_kor")                                 // 한글메뉴명
  menuNameEng     String?  @map("menu_name_eng")                                 // 영문메뉴명
  seq             Int?     @map("seq")                                           // 순서
  menuUrl         String?  @map("menu_url")                                      // 메뉴URL
  menuDesc        String?  @map("menu_desc")                                     // 메뉴설명
  writer          String?  @map("writer")                                        // 작성자
  regdate         DateTime? @map("regdate")                                      // 등록일

  // 관계 정의
  parent          MenuInfo? @relation("MenuToMenu", fields: [parentObjId], references: [objid])
  children        MenuInfo[] @relation("MenuToMenu")
  menuAuthGroups  MenuAuthGroup[]

  @@map("menu_info")
}

dept_info 테이블

model DeptInfo {
  deptCode        String   @id @map("dept_code")                                // 부서코드 (NOT NULL)
  parentDeptCode  String?  @map("parent_dept_code")                             // 상위부서코드
  deptName        String?  @map("dept_name")                                    // 부서명
  masterSabun     String?  @map("master_sabun")                                 // 마스터사번
  masterUserId    String?  @map("master_user_id")                               // 마스터사용자ID
  location        String?  @map("location")                                     // 위치
  locationName    String?  @map("location_name")                                // 위치명
  regdate         DateTime? @map("regdate")                                     // 등록일
  dataType        String?  @map("data_type")                                    // 데이터타입

  // 관계 정의
  parent          DeptInfo? @relation("DeptToDept", fields: [parentDeptCode], references: [deptCode])
  children        DeptInfo[] @relation("DeptToDept")
  users           UserInfo[]

  @@map("dept_info")
}

3. 주요 테이블 카테고리

사용자/권한 관련

  • user_info - 사용자 정보
  • user_info_history - 사용자 정보 히스토리
  • dept_info - 부서 정보
  • authority_master - 권한 마스터
  • rel_menu_auth - 메뉴 권한 관계

메뉴/시스템 관련

  • menu_info - 메뉴 정보
  • table_labels - 테이블 라벨
  • column_labels - 컬럼 라벨

다국어 관련

  • multi_lang_key_master - 다국어 키 마스터
  • multi_lang_text - 다국어 텍스트
  • language_master - 언어 마스터

비즈니스 로직 관련

  • comm_code - 공통 코드
  • company_mng - 회사 관리
  • contract_mgmt - 계약 관리
  • order_mgmt - 주문 관리
  • inventory_mgmt - 재고 관리
  • part_mgmt - 부품 관리

4. 마이그레이션 관리

# 마이그레이션 생성
npx prisma migrate dev --name add_user_table

# 마이그레이션 적용
npx prisma migrate deploy

# 스키마 동기화
npx prisma db push

5. 데이터 타입 매핑 규칙

PostgreSQL Prisma TypeScript
character varying String string
numeric Int number
timestamp without time zone DateTime Date
boolean Boolean boolean
text String string

🔐 인증 및 보안 규칙

1. JWT 토큰 구조

interface JWTPayload {
  userId: string;
  userName: string;
  email: string;
  deptCode?: string;
  permissions: string[];
  iat: number;
  exp: number;
}

2. 인증 미들웨어

export const authenticateToken = async (
  req: Request,
  res: Response,
  next: NextFunction
): Promise<void> => {
  try {
    const authHeader = req.headers.authorization;
    const token = authHeader?.split(" ")[1];

    if (!token) {
      throw new AuthError("토큰이 제공되지 않았습니다", 401);
    }

    const decoded = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;
    req.user = decoded;
    next();
  } catch (error) {
    next(new AuthError("유효하지 않은 토큰입니다", 401));
  }
};

🌐 API 설계 규칙

1. RESTful API 설계

// ✅ 권장
GET    /api/users              // 사용자 목록 조회
GET    /api/users/:id          // 특정 사용자 조회
POST   /api/users              // 사용자 생성
PUT    /api/users/:id          // 사용자 전체 수정
PATCH  /api/users/:id          // 사용자 부분 수정
DELETE /api/users/:id          // 사용자 삭제

2. 응답 형식 표준화

interface ApiResponse<T> {
  success: boolean;
  data?: T;
  message?: string;
  error?: {
    code: string;
    details?: any;
  };
  pagination?: {
    page: number;
    limit: number;
    total: number;
    totalPages: number;
  };
}

3. 컨트롤러 구현

export class UserController {
  constructor(private userService: UserService) {}

  async getUsers(
    req: Request,
    res: Response,
    next: NextFunction
  ): Promise<void> {
    try {
      const { page = 1, limit = 10, search } = req.query;
      const params: GetUsersParams = {
        page: Number(page),
        limit: Number(limit),
        search: search as string,
      };

      const result = await this.userService.getUsers(params);
      res.json(
        successResponse(result.data, "사용자 목록을 성공적으로 조회했습니다")
      );
    } catch (error) {
      next(error);
    }
  }
}

🧪 테스트 규칙

1. 테스트 구조

describe("UserController", () => {
  let userController: UserController;
  let userService: jest.Mocked<UserService>;

  beforeEach(() => {
    userService = createMockUserService();
    userController = new UserController(userService);
  });

  describe("getUsers", () => {
    it("should return users list successfully", async () => {
      const mockUsers = [createMockUser()];
      userService.getUsers.mockResolvedValue({
        data: mockUsers,
        pagination: { page: 1, limit: 10, total: 1, totalPages: 1 },
      });

      const req = createMockRequest({ page: 1, limit: 10 });
      const res = createMockResponse();
      const next = jest.fn();

      await userController.getUsers(req, res, next);

      expect(res.json).toHaveBeenCalledWith(
        expect.objectContaining({
          success: true,
          data: mockUsers,
        })
      );
    });
  });
});

🚀 배포 및 운영 규칙

1. 환경별 설정

interface Environment {
  nodeEnv: "development" | "production" | "test";
  port: number;
  database: {
    url: string;
    pool: {
      min: number;
      max: number;
    };
  };
  jwt: {
    secret: string;
    expiresIn: string;
  };
  cors: {
    origin: string[];
  };
}

2. 로깅 규칙

export const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || "info",
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: "logs/error.log", level: "error" }),
    new winston.transports.File({ filename: "logs/combined.log" }),
  ],
});

📦 필수 패키지 목록

Core Dependencies

{
  "express": "^4.18.2",
  "prisma": "^5.7.1",
  "@prisma/client": "^5.7.1",
  "pg": "^8.11.3",
  "jsonwebtoken": "^9.0.2",
  "bcryptjs": "^2.4.3",
  "helmet": "^7.1.0",
  "cors": "^2.8.5",
  "multer": "^1.4.5-lts.1",
  "nodemailer": "^6.9.7",
  "winston": "^3.11.0",
  "joi": "^17.11.0",
  "redis": "^4.6.10",
  "compression": "^1.7.4",
  "express-rate-limit": "^7.1.5",
  "dotenv": "^16.3.1"
}

Dev Dependencies

{
  "typescript": "^5.3.3",
  "@types/node": "^20.10.5",
  "@types/express": "^4.17.21",
  "@types/pg": "^8.10.9",
  "@types/jsonwebtoken": "^9.0.5",
  "@types/bcryptjs": "^2.4.6",
  "@types/cors": "^2.8.17",
  "@types/multer": "^1.4.11",
  "@types/nodemailer": "^6.4.14",
  "@types/morgan": "^1.9.9",
  "@types/compression": "^1.7.5",
  "@types/sanitize-html": "^2.9.5",
  "@types/node-cron": "^3.0.11",
  "@types/fs-extra": "^11.0.4",
  "@types/csv-parser": "^1.2.3",
  "jest": "^29.7.0",
  "@types/jest": "^29.5.11",
  "supertest": "^6.3.3",
  "@types/supertest": "^6.0.2",
  "ts-jest": "^29.1.1",
  "nodemon": "^3.0.2",
  "ts-node": "^10.9.2",
  "eslint": "^8.55.0",
  "@typescript-eslint/eslint-plugin": "^6.14.0",
  "@typescript-eslint/parser": "^6.14.0",
  "prettier": "^3.1.0"
}

📋 마이그레이션 체크리스트

Phase 1: 기반 구축 (1-2주) - 완료

  • Node.js + TypeScript 프로젝트 설정
  • 기존 데이터베이스 스키마 분석 (docs/Database_Schema_Collection.md 참고)
  • Prisma 스키마 설계 및 마이그레이션
  • 기본 인증 시스템 구현
  • 에러 처리 및 로깅 설정
  • 서버 실행 및 포트 설정 (8080 포트)

🔄 Phase 2: 핵심 API 개발 (4-6주) - 진행 중

Phase 2-1: 인증 시스템 마이그레이션 (2주) - 우선 진행

Phase 2-1A: 기본 인증 구조 (1주) - 완료

  • 기존 인증 타입 정의 (LoginRequest, UserInfo, ApiResponse)
  • 비밀번호 암호화 유틸리티 (기존 EncryptUtil 포팅)
  • JWT 토큰 관리 유틸리티
  • 인증 미들웨어 구현

Phase 2-1B: 핵심 인증 API (1주) - 완료

  • 로그인 API (POST /api/auth/login) - 기존 ApiLoginController.login() 포팅
  • 사용자 정보 API (GET /api/auth/me) - 기존 getCurrentUser() 포팅
  • 로그아웃 API (POST /api/auth/logout) - 기존 logout() 포팅
  • 인증 상태 확인 API (GET /api/auth/status) - 기존 checkAuthStatus() 포팅
  • 로그인 로그 기록 기능 (기존 insertLoginAccessLog() 포팅)
  • 데이터베이스 스키마 동기화 (Prisma db pull)
  • 실제 데이터베이스 연결 및 테스트
  • 대소문자 처리 문제 해결
  • 로그인 API 성공 테스트 완료

Phase 2-2: 사용자 관리 API (1주)

  • 사용자 목록 조회 API (user_info 테이블 기반)
  • 사용자 상세 조회 API
  • 사용자 생성/수정 API
  • 사용자 비밀번호 변경 API

Phase 2-2A: 사용자 관리 기능 Node.js 리팩토링 계획 (1주)

📋 사용자 관리 기능 Node.js 리팩토링 개요

목표: 기존 Java Spring Boot의 사용자 관리 기능을 Node.js + TypeScript로 완전 리팩토링

기존 Java 백엔드 (@backend/) 분석

  • Spring Framework 기반의 AdminControllerAdminService
  • MyBatis를 사용한 데이터베이스 접근
  • 사용자 CRUD, 권한 관리, 상태 변경 등 완전한 기능 구현

현재 Node.js 백엔드 (@backend-node/) 상황

  • 기본적인 사용자 목록 조회만 더미 데이터로 구현
  • 실제 데이터베이스 연동 부족
  • 사용자 관리 핵심 기능 미구현

🎯 리팩토링 목표

  1. 기존 Java 백엔드의 사용자 관리 기능을 Node.js로 완전 이전
  2. Prisma ORM을 활용한 데이터베이스 연동
  3. 기존 API 응답 형식과 호환성 유지
  4. 보안 및 인증 기능 강화

🛠️ 단계별 구현 계획

Phase 2-2A-1: 데이터베이스 스키마 및 모델 정리 (1일)

  • Prisma 스키마에 user_info 테이블 정의 완료
  • 사용자 관련 추가 테이블 스키마 확인 (부서, 권한 등)
  • 데이터 타입 및 관계 정의

Phase 2-2A-2: 핵심 사용자 관리 API 구현 (3일)

사용자 CRUD API

// 기존 Java AdminController의 핵심 메서드들
- GET /api/admin/users - 사용자 목록 조회 (페이징, 검색)
- GET /api/admin/users/:userId - 사용자 상세 조회
- POST /api/admin/users - 사용자 등록/수정
- PUT /api/admin/users/:userId/status - 사용자 상태 변경
- DELETE /api/admin/users/:userId - 사용자 삭제

사용자 관리 부가 기능

- POST /api/admin/users/check-duplicate - 사용자 ID 중복 체크
- POST /api/admin/users/reset-password - 비밀번호 초기화
- GET /api/admin/users/:userId/history - 사용자 변경 이력
- GET /api/admin/departments - 부서 목록 조회

Phase 2-2A-3: 서비스 레이어 구현 (2일)

AdminService 확장

// 기존 Java AdminService의 핵심 메서드들
- getEtcUserList() - 사용자 목록 조회
- getEtcUserInfo() - 사용자 상세 정보
- saveEtcUserInfo() - 사용자 저장/수정
- checkDuplicateEtcUserId() - 중복 체크
- changeUserStatus() - 상태 변경
- getUserHistoryList() - 변경 이력

데이터베이스 연동

- Prisma ORM을 사용한 PostgreSQL 연동
- 트랜잭션 처리
- 에러 핸들링  로깅

Phase 2-2A-4: 보안 및 검증 강화 (1일)

입력값 검증

- 사용자 입력 데이터 검증
- SQL 인젝션 방지
- XSS 방지

권한 관리

- 사용자별 권한 체크
- 메뉴 접근 권한 검증
- 역할 기반 접근 제어 (RBAC)

🔄 구현 우선순위

High Priority (1-2일차)

  1. 사용자 목록 조회 API (실제 DB 연동)
  2. 사용자 상세 조회 API
  3. 사용자 등록/수정 API
  4. 기본적인 에러 핸들링

Medium Priority (3-4일차)

  1. 사용자 상태 변경 API
  2. 사용자 ID 중복 체크 API
  3. 부서 목록 조회 API
  4. 페이징 및 검색 기능

Low Priority (5일차)

  1. 사용자 변경 이력 API
  2. 비밀번호 초기화 API
  3. 사용자 삭제 API
  4. 고급 검색 및 필터링

🔧 기술적 고려사항

데이터베이스 연동

  • Prisma ORM 사용으로 타입 안전성 확보
  • 기존 PostgreSQL 스키마와 호환성 유지
  • 마이그레이션 스크립트 작성

API 호환성

  • 기존 Java 백엔드와 동일한 응답 형식 유지
  • 프론트엔드 변경 최소화
  • 점진적 마이그레이션 지원

성능 최적화

  • 데이터베이스 인덱스 활용
  • 쿼리 최적화
  • 캐싱 전략 수립

📊 테스트 계획

  1. 단위 테스트: 각 서비스 메서드별 테스트
  2. 통합 테스트: API 엔드포인트별 테스트
  3. 데이터베이스 테스트: 실제 DB 연동 테스트
  4. 성능 테스트: 대용량 데이터 처리 테스트

📝 기존 Java 코드 분석 결과

AdminController 주요 메서드

// 사용자 목록 조회
@RequestMapping("/admin/userMngList.do")
public String userMngList(HttpServletRequest request, @RequestParam Map paramMap)

// 사용자 정보 저장
@RequestMapping("/admin/saveUserInfo.do")
public String saveUserInfo(HttpServletRequest request, @RequestParam Map paramMap)

// 사용자 ID 중복 체크
@RequestMapping("/admin/checkDuplicateUserId.do")
public String checkDuplicateUserId(HttpServletRequest request, @RequestParam Map paramMap)

// 사용자 상태 변경
@RequestMapping("/admin/changeUserStatus.do")
public String changeUserStatus(HttpServletRequest request, @RequestParam Map paramMap)

AdminService 주요 메서드

// 사용자 목록 조회
public List<Map<String, Object>> getEtcUserList(HttpServletRequest request, Map<String, Object> paramMap)

// 사용자 정보 저장
public Map<String, Object> saveEtcUserInfo(HttpServletRequest request, Map<String, Object> paramMap)

// 사용자 ID 중복 체크
public Map<String, Object> checkDuplicateEtcUserId(Map<String, Object> paramMap)

// 사용자 상태 변경
public Map<String, Object> changeUserStatus(Map<String, Object> paramMap)

📋 다음 단계

이 계획에 따라 Phase 2-2A를 시작하여 단계적으로 사용자 관리 기능을 구현하겠습니다.

시작 지점: 사용자 목록 조회 API부터 실제 데이터베이스 연동으로 구현

🗄️ 테이블 타입관리 백엔드 Node.js 리팩토링 계획

📋 테이블 타입관리 기능 Node.js 리팩토링 개요

목표: 기존 Java Spring Boot의 테이블 타입관리 기능을 Node.js + TypeScript로 완전 리팩토링

기존 Java 백엔드 (@backend/) 분석

  • Spring Framework 기반의 TableManagementControllerTableManagementService
  • MyBatis를 사용한 PostgreSQL 메타데이터 조회
  • table_labels, column_labels 테이블을 통한 테이블/컬럼 설정 관리
  • information_schema 활용한 데이터베이스 구조 자동 조회

현재 Node.js 백엔드 (@backend-node/) 상황

  • 테이블 타입관리 기능 미구현
  • PostgreSQL 메타데이터 조회 기능 부재
  • 컬럼 설정 관리 기능 부재

🎯 리팩토링 목표

  1. 기존 Java 백엔드의 테이블 타입관리 기능을 Node.js로 완전 이전
  2. PostgreSQL information_schema 활용한 메타데이터 조회
  3. table_labels, column_labels 테이블을 통한 설정 관리
  4. 기존 API 응답 형식과 호환성 유지

🛠️ 단계별 구현 계획

Phase 2-5-1: 데이터베이스 스키마 및 모델 정리 (1일)

  • Prisma 스키마에 table_labels, column_labels 테이블 정의
  • PostgreSQL 메타데이터 조회를 위한 권한 설정 확인
  • 데이터 타입 및 관계 정의

Phase 2-5-2: 핵심 테이블 타입관리 API 구현 (3일)

테이블 메타데이터 API

// 기존 Java TableManagementController의 핵심 메서드들
- GET /api/table-management/tables - 테이블 목록 조회 (information_schema 기반)
- GET /api/table-management/tables/:tableName/columns - 컬럼 정보 조회
- POST /api/table-management/tables/:tableName/columns/:columnName/settings - 개별 컬럼 설정 업데이트
- POST /api/table-management/tables/:tableName/columns/settings - 전체 컬럼 설정 일괄 업데이트

PostgreSQL 메타데이터 조회

// information_schema를 활용한 테이블 목록 조회
const getTableList = async (): Promise<TableInfo[]> => {
  const query = `
    SELECT 
      t.table_name as "tableName",
      COALESCE(tl.table_label, t.table_name) as "displayName",
      COALESCE(tl.description, '') as "description",
      (SELECT COUNT(*) FROM information_schema.columns 
       WHERE table_name = t.table_name AND table_schema = 'public') as "columnCount"
    FROM information_schema.tables t
    LEFT JOIN table_labels tl ON t.table_name = tl.table_name
    WHERE t.table_schema = 'public' 
      AND t.table_type = 'BASE TABLE'
      AND t.table_name NOT LIKE 'pg_%'
      AND t.table_name NOT LIKE 'sql_%'
    ORDER BY t.table_name
  `;

  const result = await client.query(query);
  return result.rows;
};

Phase 2-5-3: 서비스 레이어 구현 (2일)

TableManagementService 확장

// 기존 Java TableManagementService의 핵심 메서드들
- getTableList() - 테이블 목록 조회 (information_schema 활용)
- getColumnList(tableName) - 컬럼 정보 조회
- updateColumnSettings() - 개별 컬럼 설정 업데이트
- updateAllColumnSettings() - 전체 컬럼 설정 일괄 업데이트

데이터베이스 연동

- PostgreSQL 클라이언트를 사용한 직접 쿼리 실행
- 트랜잭션 처리
- 에러 핸들링  로깅

Phase 2-5-4: 컬럼 설정 관리 기능 (1일)

컬럼 설정 데이터 구조

interface ColumnSettings {
  columnLabel: string; // 컬럼 표시명
  webType: string; // 웹 입력 타입 (text, number, date, code, entity)
  detailSettings: string; // 상세 설정
  codeCategory: string; // 코드 카테고리
  codeValue: string; // 코드 값
  referenceTable: string; // 참조 테이블
  referenceColumn: string; // 참조 컬럼
}

UPSERT 방식으로 컬럼 설정 저장

const updateColumnSettings = async (
  tableName: string,
  columnName: string,
  settings: ColumnSettings
): Promise<void> => {
  const query = `
    INSERT INTO column_labels (
      table_name, column_name, column_label, web_type, 
      detail_settings, code_category, code_value, 
      reference_table, reference_column
    ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
    ON CONFLICT (table_name, column_name) DO UPDATE SET
      column_label = EXCLUDED.column_label,
      web_type = EXCLUDED.web_type,
      detail_settings = EXCLUDED.detail_settings,
      code_category = EXCLUDED.code_category,
      code_value = EXCLUDED.code_value,
      reference_table = EXCLUDED.reference_table,
      reference_column = EXCLUDED.reference_column,
      updated_date = now()
  `;

  await client.query(query, [
    tableName,
    columnName,
    settings.columnLabel,
    settings.webType,
    settings.detailSettings,
    settings.codeCategory,
    settings.codeValue,
    settings.referenceTable,
    settings.referenceColumn,
  ]);
};

🔄 구현 우선순위

High Priority (1-2일차)

  1. 테이블 목록 조회 API (information_schema 활용)
  2. 컬럼 정보 조회 API
  3. 기본적인 에러 핸들링

Medium Priority (3-4일차)

  1. 개별 컬럼 설정 업데이트 API
  2. 전체 컬럼 설정 일괄 업데이트 API
  3. 테이블/컬럼 라벨 자동 생성 기능

Low Priority (5일차)

  1. 컬럼 설정 검증 로직
  2. 메타데이터 캐싱 기능
  3. 고급 검색 및 필터링

🔧 기술적 고려사항

PostgreSQL 메타데이터 조회

  • information_schema 접근 권한 확인
  • 시스템 테이블 제외 로직 (pg_*, sql_* 테이블 제외)
  • 성능 최적화를 위한 인덱스 활용

데이터베이스 연동

  • Prisma ORM 대신 PostgreSQL 클라이언트 직접 사용
  • 메타데이터 조회를 위한 특수 쿼리 처리
  • 트랜잭션 관리 및 롤백 처리

API 호환성

  • 기존 Java 백엔드와 동일한 응답 형식 유지
  • 프론트엔드 변경 최소화
  • 점진적 마이그레이션 지원

📊 테스트 계획

  1. 단위 테스트: 각 서비스 메서드별 테스트
  2. 통합 테스트: API 엔드포인트별 테스트
  3. 데이터베이스 테스트: 실제 PostgreSQL 메타데이터 조회 테스트
  4. 성능 테스트: 대용량 테이블/컬럼 조회 테스트

📝 기존 Java 코드 분석 결과

TableManagementController 주요 메서드

// 테이블 목록 조회
@GetMapping("/tables")
public ResponseEntity<Map<String, Object>> getTableList(HttpServletRequest request)

// 컬럼 정보 조회
@GetMapping("/tables/{tableName}/columns")
public ResponseEntity<Map<String, Object>> getColumnList(
    HttpServletRequest request, @PathVariable String tableName)

// 컬럼 설정 업데이트
@PostMapping("/tables/{tableName}/columns/{columnName}/settings")
public ResponseEntity<Map<String, Object>> updateColumnSettings(
    HttpServletRequest request, @PathVariable String tableName,
    @PathVariable String columnName, @RequestBody Map<String, Object> settings)

// 전체 컬럼 설정 일괄 업데이트
@PostMapping("/tables/{tableName}/columns/settings")
public ResponseEntity<Map<String, Object>> updateAllColumnSettings(
    HttpServletRequest request, @PathVariable String tableName,
    @RequestBody List<Map<String, Object>> columnSettings)

TableManagementService 주요 메서드

// 테이블 목록 조회
public List<Map<String, Object>> getTableList()

// 컬럼 정보 조회
public List<Map<String, Object>> getColumnList(String tableName)

// 컬럼 설정 업데이트
public void updateColumnSettings(String tableName, String columnName, Map<String, Object> settings)

// 전체 컬럼 설정 일괄 업데이트
public void updateAllColumnSettings(String tableName, List<Map<String, Object>> columnSettings)

MyBatis Mapper 주요 쿼리

<!-- 테이블 목록 조회 -->
<select id="selectTableList" resultType="map">
    SELECT
        t.table_name as "tableName",
        COALESCE(tl.table_label, t.table_name) as "displayName",
        COALESCE(tl.description, '') as "description",
        (SELECT COUNT(*) FROM information_schema.columns
         WHERE table_name = t.table_name AND table_schema = 'public') as "columnCount"
    FROM information_schema.tables t
    LEFT JOIN table_labels tl ON t.table_name = tl.table_name
    WHERE t.table_schema = 'public'
      AND t.table_type = 'BASE TABLE'
      AND t.table_name NOT LIKE 'pg_%'
      AND t.table_name NOT LIKE 'sql_%'
    ORDER BY t.table_name
</select>

<!-- 컬럼 정보 조회 -->
<select id="selectColumnList" parameterType="map" resultType="map">
    SELECT
        c.column_name as "columnName",
        COALESCE(cl.column_label, c.column_name) as "displayName",
        c.data_type as "dbType",
        COALESCE(cl.web_type, 'text') as "webType",
        COALESCE(cl.detail_settings, '') as "detailSettings",
        COALESCE(cl.description, '') as "description",
        c.is_nullable as "isNullable",
        c.column_default as "defaultValue",
        c.character_maximum_length as "maxLength",
        c.numeric_precision as "numericPrecision",
        c.numeric_scale as "numericScale",
        cl.code_category as "codeCategory",
        cl.code_value as "codeValue",
        cl.reference_table as "referenceTable",
        cl.reference_column as "referenceColumn"
    FROM information_schema.columns c
    LEFT JOIN column_labels cl ON c.table_name = cl.table_name AND c.column_name = cl.column_name
    WHERE c.table_name = #{tableName}
    ORDER BY c.ordinal_position
</select>

📋 다음 단계

이 계획에 따라 Phase 2-5를 시작하여 단계적으로 테이블 타입관리 기능을 구현하겠습니다.

시작 지점: 테이블 목록 조회 API부터 PostgreSQL 메타데이터 조회로 구현

Phase 2-2A: 메뉴 관리 API (완료 )

  • 관리자 메뉴 조회 API (GET /api/admin/menus) - 완료: 기존 AdminController.getAdminMenuList() 포팅
  • 사용자 메뉴 조회 API (GET /api/admin/user-menus) - 완료: 기존 AdminController.getUserMenuList() 포팅
  • 메뉴 정보 조회 API (GET /api/admin/menus/:menuId) - 완료: 기존 AdminController.getMenuInfo() 포팅
  • JWT 토큰 인증 미들웨어 적용
  • Prisma $queryRaw를 사용한 복잡한 재귀 쿼리 포팅
  • 환경변수 설정 및 데이터베이스 연결 문제 해결
  • 프론트엔드 API 클라이언트에 JWT 토큰 자동 추가
  • 401/500 오류 해결 및 정상 작동 확인

Phase 2-3: 부서 관리 API (1주)

  • 부서 목록 조회 API (dept_info 테이블 기반)
  • 부서 트리 구조 API
  • 부서 생성/수정 API

Phase 2-4: 메뉴 및 권한 관리 API (1주)

  • 메뉴 관리 API (menu_info 테이블 기반)
  • 권한 관리 API (authority_master, rel_menu_auth 테이블 기반)
  • 사용자별 메뉴 권한 조회 API

Phase 2-5: 테이블 타입관리 API (1주)

  • 테이블 타입관리 API (table_labels, column_labels 테이블 기반)
  • PostgreSQL 메타데이터 조회 API (information_schema 활용)
  • 컬럼 설정 관리 API (웹 타입, 참조 테이블, 코드 카테고리 등)

Phase 2-6: 다국어 및 공통 관리 API (1주)

  • 다국어 관리 API (multi_lang_key_master, multi_lang_text 테이블 기반)
  • 공통 코드 관리 API (comm_code 테이블 기반)

Phase 3: 비즈니스 로직 API (3-4주)

  • 회사 관리 API (company_mng 테이블 기반)
  • 계약 관리 API (contract_mgmt 테이블 기반)
  • 주문 관리 API (order_mgmt 테이블 기반)
  • 재고 관리 API (inventory_mgmt 테이블 기반)
  • 부품 관리 API (part_mgmt 테이블 기반)

Phase 4: 고급 기능 (2-3주)

  • 파일 업로드/다운로드 (attach_file_info 테이블 기반)
  • Excel 처리 기능
  • 이메일 발송 기능 (mail_log 테이블 기반)
  • 배치 처리 기능
  • 로그 관리 API (login_access_log 테이블 기반)

Phase 5: 테스트 및 최적화 (1-2주)

  • 단위 테스트 작성
  • 통합 테스트 작성
  • 성능 최적화
  • 보안 검증

Phase 6: 배포 및 운영 (1주)

  • Docker 컨테이너화
  • CI/CD 파이프라인 구축
  • 모니터링 설정
  • 문서화

⚠️ 주의사항

  1. 기존 데이터 보존: 마이그레이션 시 기존 데이터 손실 방지
  2. API 호환성: 프론트엔드와의 호환성 유지
  3. 보안: 인증/인가 로직 완전 재구현
  4. 성능: 데이터베이스 쿼리 최적화
  5. 테스트: 모든 기능에 대한 테스트 코드 작성
  6. 스키마 참고: docs/Database_Schema_Collection.md 문서를 항상 참고하여 정확한 테이블 구조 반영
  7. 데이터 타입 매핑: PostgreSQL → Prisma → TypeScript 타입 매핑 정확성 확인
  8. 관계 설정: 외래키 관계를 Prisma 관계로 정확히 매핑
  9. 히스토리 테이블: *_history 테이블들의 처리 방안 수립
  10. 임시 테이블: temp*, *_temp 테이블들의 정리 및 제거 계획 수립
  11. 메뉴 API 완료: /api/admin/menus/api/admin/user-menus API가 성공적으로 구현되어 프론트엔드 메뉴 표시가 정상 작동
  12. JWT 토큰 관리: 프론트엔드 API 클라이언트에서 JWT 토큰을 자동으로 포함하여 인증 문제 해결
  13. 환경변수 관리: Prisma 스키마에서 직접 데이터베이스 URL 설정으로 환경변수 로딩 문제 해결
  14. 어드민 메뉴 인증: 새 탭에서 열리는 어드민 페이지의 토큰 인증 문제 해결 - localStorage 공유 활용
  15. 관리자 메뉴 내 페이지 이동 토큰 문제: 레이아웃 레벨 토큰 확인 및 동기화 구현
  16. API 클라이언트 통일: 모든 API에서 apiClient 사용으로 토큰 자동 전달 보장
  17. 토큰 동기화 유틸리티: localStorage와 sessionStorage 간 토큰 동기화 및 복원 기능

🔐 인증 및 보안 가이드

어드민 메뉴 토큰 인증 문제 해결

문제 상황

  • 어드민 버튼 클릭 시 새 탭에서 어드민 페이지가 열림
  • 새 탭에서 토큰 인증 문제 발생 가능성
  • URL 파라미터로 토큰 전달은 보안상 위험

해결 방안 (권장)

1. localStorage 공유 활용 (가장 간단)

// AdminButton.tsx - 수정 없음
const handleAdminClick = () => {
  const adminUrl = `${window.location.origin}/admin`;
  window.open(adminUrl, "_blank");
};

// admin/page.tsx - AuthGuard 적용
("use client");
import { AuthGuard } from "@/components/auth/AuthGuard";
import { CompanyManagement } from "@/components/admin/CompanyManagement";

export default function AdminPage() {
  return (
    <AuthGuard requireAdmin={true}>
      <CompanyManagement />
    </AuthGuard>
  );
}

2. BroadcastChannel API 활용 (고급)

// utils/tabCommunication.ts
export class TabCommunication {
  private channel: BroadcastChannel;

  constructor() {
    this.channel = new BroadcastChannel("auth-channel");
  }

  // 토큰 요청
  requestToken(): Promise<string | null> {
    return new Promise((resolve) => {
      const timeout = setTimeout(() => {
        resolve(localStorage.getItem("authToken"));
      }, 100);

      this.channel.postMessage({ type: "REQUEST_TOKEN" });

      const handler = (event: MessageEvent) => {
        if (event.data.type === "TOKEN_RESPONSE") {
          clearTimeout(timeout);
          this.channel.removeEventListener("message", handler);
          resolve(event.data.token);
        }
      };

      this.channel.addEventListener("message", handler);
    });
  }
}

3. 쿠키 기반 토큰 (가장 안전)

// backend-node/src/controllers/authController.ts
static async login(req: Request, res: Response): Promise<void> {
  // HTTPOnly 쿠키로 토큰 설정
  res.cookie('authToken', token, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    maxAge: 24 * 60 * 60 * 1000, // 24시간
  });
}

보안 고려사항

  1. URL 파라미터 사용 금지: 토큰이 URL에 노출되어 보안 위험
  2. HTTPS 필수: 프로덕션 환경에서는 반드시 HTTPS 사용
  3. 토큰 만료 처리: 자동 갱신 또는 재로그인 유도
  4. CSRF 방지: 토큰 기반 요청 검증
  5. 로그아웃 처리: 모든 탭에서 토큰 제거

구현 우선순위

1단계 (즉시 적용)

  • AuthGuard를 사용한 어드민 페이지 보호
  • localStorage 공유 활용

2단계 (1-2일 내)

  • 토큰 유효성 검증 API 추가
  • 에러 처리 개선

3단계 (3-5일 내)

  • 세션 관리 개선
  • 토큰 갱신 로직 추가

JWT 토큰 관리 모범 사례

프론트엔드 토큰 관리

// lib/api/client.ts
const TokenManager = {
  getToken: (): string | null => {
    if (typeof window !== "undefined") {
      return localStorage.getItem("authToken");
    }
    return null;
  },

  isTokenExpired: (token: string): boolean => {
    try {
      const payload = JSON.parse(atob(token.split(".")[1]));
      return payload.exp * 1000 < Date.now();
    } catch {
      return true;
    }
  },
};

토큰 동기화 유틸리티

// lib/sessionManager.ts
export const tokenSync = {
  // 토큰 상태 확인
  checkToken: () => {
    const token = localStorage.getItem("authToken");
    console.log("🔍 토큰 상태 확인:", token ? "존재" : "없음");
    return !!token;
  },

  // 토큰 강제 동기화 (다른 탭에서 설정된 토큰을 현재 탭에 복사)
  forceSync: () => {
    const token = localStorage.getItem("authToken");
    if (token) {
      // sessionStorage에도 복사
      sessionStorage.setItem("authToken", token);
      console.log("🔄 토큰 강제 동기화 완료");
      return true;
    }
    return false;
  },

  // 토큰 복원 시도 (sessionStorage에서 복원)
  restoreFromSession: () => {
    const sessionToken = sessionStorage.getItem("authToken");
    if (sessionToken) {
      localStorage.setItem("authToken", sessionToken);
      console.log("🔄 sessionStorage에서 토큰 복원 완료");
      return true;
    }
    return false;
  },

  // 토큰 유효성 검증
  validateToken: (token: string) => {
    if (!token) return false;

    try {
      // JWT 토큰 구조 확인 (header.payload.signature)
      const parts = token.split(".");
      if (parts.length !== 3) return false;

      // payload 디코딩 시도
      const payload = JSON.parse(atob(parts[1]));
      const now = Math.floor(Date.now() / 1000);

      // 만료 시간 확인
      if (payload.exp && payload.exp < now) {
        console.log("❌ 토큰 만료됨");
        return false;
      }

      console.log("✅ 토큰 유효성 검증 통과");
      return true;
    } catch (error) {
      console.log("❌ 토큰 유효성 검증 실패:", error);
      return false;
    }
  },
};

관리자 레이아웃 토큰 확인

// app/(main)/admin/layout.tsx
export default function AdminLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const [isAuthorized, setIsAuthorized] = useState<boolean | null>(null);

  // 토큰 확인 및 인증 상태 체크
  useEffect(() => {
    const checkToken = () => {
      const token = localStorage.getItem("authToken");

      // 토큰이 없으면 sessionStorage에서 복원 시도
      if (!token && sessionToken) {
        const restored = tokenSync.restoreFromSession();
        if (restored) {
          setIsAuthorized(true);
          return;
        }
      }

      // 토큰 유효성 검증
      if (token && !tokenSync.validateToken(token)) {
        localStorage.removeItem("authToken");
        sessionStorage.removeItem("authToken");
        setIsAuthorized(false);
        return;
      }

      if (!token) {
        setIsAuthorized(false);
        return;
      }

      // 토큰이 있으면 인증된 것으로 간주
      setIsAuthorized(true);

      // 토큰 강제 동기화 (다른 탭과 동기화)
      tokenSync.forceSync();
    };

    // 초기 토큰 확인
    checkToken();

    // localStorage 변경 이벤트 리스너 추가
    const handleStorageChange = (e: StorageEvent) => {
      if (e.key === "authToken") {
        checkToken();
      }
    };

    // 페이지 포커스 시 토큰 재확인
    const handleFocus = () => {
      checkToken();
    };

    window.addEventListener("storage", handleStorageChange);
    window.addEventListener("focus", handleFocus);

    return () => {
      window.removeEventListener("storage", handleStorageChange);
      window.removeEventListener("focus", handleFocus);
    };
  }, [pathname]);
}

API 클라이언트 통일

// lib/api/user.ts - 수정 전 (fetch 사용)
async function apiCall<T = any>(
  endpoint: string,
  options: RequestInit = {}
): Promise<ApiResponse<T>> {
  const response = await fetch(`${API_BASE_URL}${endpoint}`, {
    headers: {
      "Content-Type": "application/json",
      ...options.headers,
    },
    credentials: "include",
    ...options,
  });
  // 토큰 수동 추가 필요
}

// lib/api/user.ts - 수정 후 (apiClient 사용)
export async function getUserList(params?: Record<string, any>) {
  try {
    const response = await apiClient.get("/admin/users", {
      params: params,
    });
    // 토큰 자동 추가됨
    return response.data;
  } catch (error) {
    console.error("❌ 사용자 목록 API 오류:", error);
    throw error;
  }
}

백엔드 토큰 검증

// middleware/authMiddleware.ts
export const authenticateToken = (
  req: AuthenticatedRequest,
  res: Response,
  next: NextFunction
): void => {
  try {
    const authHeader = req.get("Authorization");
    const token = authHeader && authHeader.split(" ")[1];

    if (!token) {
      res.status(401).json({
        success: false,
        error: {
          code: "TOKEN_MISSING",
          details: "인증 토큰이 필요합니다.",
        },
      });
      return;
    }

    const userInfo: PersonBean = JwtUtils.verifyToken(token);
    req.user = userInfo;
    next();
  } catch (error) {
    res.status(401).json({
      success: false,
      error: {
        code: "INVALID_TOKEN",
        details: "토큰 검증에 실패했습니다.",
      },
    });
  }
};

토큰 인증 문제 해결 완료 사항

해결된 문제들

  1. 어드민 메뉴 토큰 인증 문제

    • 새 탭에서 열리는 어드민 페이지의 토큰 공유
    • localStorage 기반 토큰 동기화
  2. 관리자 메뉴 내 페이지 이동 시 토큰 문제

    • 레이아웃 레벨에서 토큰 확인 로직 추가
    • 실시간 토큰 동기화 및 검증
  3. 사용자 관리 메뉴 특정 인증 문제

    • API 클라이언트 통일 (fetch → apiClient)
    • 토큰 자동 전달 활성화

🔧 구현된 기능들

  • 토큰 동기화 유틸리티: tokenSync 모듈
  • 강화된 인증 체크: 레이아웃 레벨 토큰 검증
  • API 클라이언트 통일: 모든 API에서 토큰 자동 전달
  • 디버깅 도구: 상세한 토큰 상태 확인 및 API 테스트

📝 테스트 방법

  1. Admin 버튼 클릭 → 어드민 페이지 열기
  2. 사이드바 메뉴 클릭 → 다른 관리자 페이지로 이동
  3. 디버깅 페이지 확인/admin/debug-layout에서 토큰 상태 확인
  4. API 테스트 → 각 메뉴에서 API 호출 정상 작동 확인

🎯 성공 지표

  1. 성능 개선: API 응답 시간 30% 단축
  2. 개발 생산성: 새로운 기능 개발 시간 50% 단축
  3. 유지보수성: 코드 복잡도 감소
  4. 확장성: 마이크로서비스 아키텍처 준비

마지막 업데이트: 2024년 12월 20일 버전: 2.0.0 작성자: AI Assistant 현재 상태: Phase 1 완료, Phase 2-1A 완료, Phase 2-1B 완료, Phase 2-2A 완료 (메뉴 API 구현 완료, 어드민 메뉴 인증 문제 해결, 토큰 인증 문제 완전 해결), Phase 2-5 계획 수립 (테이블 타입관리 백엔드 Node.js 리팩토링 계획 완성)