Files
vexplor/docs/메뉴_회사별_필터링_구현_완료.md
2025-10-27 16:58:43 +09:00

8.0 KiB

메뉴 회사별 필터링 구현 완료

📋 개요

로그인한 사용자의 회사 코드에 따라 좌측 사이드바 메뉴가 필터링되어 표시되도록 구현했습니다.

🎯 구현 내용

1. 백엔드 수정

adminController.ts 수정

  • getAdminMenus(): 관리자 메뉴 조회 시 사용자 정보 전달
  • getUserMenus(): 사용자 메뉴 조회 시 사용자 정보 전달
// 추가된 파라미터
const userId = req.user?.userId;
const userCompanyCode = req.user?.companyCode || "ILSHIN";
const userType = req.user?.userType;

adminService.ts 수정

  • getAdminMenuList(): 회사별 필터링 로직 추가
  • getUserMenuList(): 회사별 필터링 로직 추가

2. 회사별 필터링 규칙

SUPER_ADMIN (userType='SUPER_ADMIN', companyCode='*')

  • 모든 회사의 메뉴 표시
  • 제한 없음
-- 필터 없음
WHERE STATUS = 'active'

COMPANY_ADMIN (userType='COMPANY_ADMIN')

  • 자기 회사 메뉴 + 공통 메뉴 표시
  • company_code = 사용자회사코드 OR company_code IS NULL
-- 회사 필터 적용
WHERE STATUS = 'active'
  AND (MENU.COMPANY_CODE = '사용자회사코드' OR MENU.COMPANY_CODE IS NULL)

일반 사용자 (userType='USER')

  • 자기 회사 메뉴 + 공통 메뉴 표시
  • COMPANY_ADMIN과 동일한 필터링
-- 회사 필터 적용
WHERE STATUS = 'active'
  AND (MENU.COMPANY_CODE = '사용자회사코드' OR MENU.COMPANY_CODE IS NULL)

📊 데이터베이스 구조

menu_info 테이블

CREATE TABLE menu_info (
  objid NUMERIC PRIMARY KEY,
  menu_name_kor VARCHAR(100),
  menu_url VARCHAR(200),
  menu_type NUMERIC,  -- 0: 관리자 메뉴, 1: 사용자 메뉴
  company_code VARCHAR(50),  -- 회사 코드 (NULL: 공통 메뉴)
  parent_obj_id NUMERIC,
  seq NUMERIC,
  status VARCHAR(20),  -- 'active', 'inactive'
  ...
);

데이터베이스 함수 활용

마이그레이션 031_add_menu_auth_columns.sql에서 생성한 함수들:

  • get_user_menus_with_permissions(): 사용자별 메뉴 권한 조회
  • check_menu_crud_permission(): 메뉴별 CRUD 권한 확인

🧪 테스트 시나리오

테스트 1: SUPER_ADMIN 로그인

기대 결과: 모든 회사의 메뉴가 표시됨

# 로그인 사용자: admin (SUPER_ADMIN, company_code='*')
# 예상 메뉴:
# - ILSHIN 회사 메뉴
# - VEXPLOR 회사 메뉴
# - 공통 메뉴 (company_code IS NULL)

테스트 2: COMPANY_ADMIN 로그인 (ILSHIN)

기대 결과: ILSHIN 회사 메뉴 + 공통 메뉴만 표시

# 로그인 사용자: ilshin_admin (COMPANY_ADMIN, company_code='ILSHIN')
# 예상 메뉴:
# - ILSHIN 회사 메뉴
# - 공통 메뉴 (company_code IS NULL)
# ❌ VEXPLOR 회사 메뉴 (표시 안 됨)

테스트 3: COMPANY_ADMIN 로그인 (VEXPLOR)

기대 결과: VEXPLOR 회사 메뉴 + 공통 메뉴만 표시

# 로그인 사용자: vexplor_admin (COMPANY_ADMIN, company_code='VEXPLOR')
# 예상 메뉴:
# - VEXPLOR 회사 메뉴
# - 공통 메뉴 (company_code IS NULL)
# ❌ ILSHIN 회사 메뉴 (표시 안 됨)

테스트 4: 일반 사용자 로그인

기대 결과: 자기 회사 메뉴 + 공통 메뉴만 표시

# 로그인 사용자: user1 (USER, company_code='ILSHIN')
# 예상 메뉴:
# - ILSHIN 회사 메뉴 (권한이 있는 메뉴만)
# - 공통 메뉴 (company_code IS NULL)

📝 로그 확인

백엔드 로그에서 다음과 같은 메시지를 확인할 수 있습니다:

# SUPER_ADMIN
✅ SUPER_ADMIN 모드: 모든 메뉴 표시

# COMPANY_ADMIN
✅ COMPANY_ADMIN 모드: 회사 ILSHIN 메뉴 + 공통 메뉴 표시

# 일반 사용자
✅ 일반 사용자 모드: 회사 ILSHIN 메뉴 + 공통 메뉴 표시

🔧 수정된 파일

백엔드

  1. /backend-node/src/controllers/adminController.ts

    • getAdminMenus(): 회사별 필터링 파라미터 전달
    • getUserMenus(): 회사별 필터링 파라미터 전달
  2. /backend-node/src/services/adminService.ts

    • getAdminMenuList(): 회사별 필터링 쿼리 적용
    • getUserMenuList(): 회사별 필터링 쿼리 적용

프론트엔드

  • 변경 없음 (기존 API 호출 방식 유지)

🎨 UI 동작

좌측 사이드바 메뉴

// frontend/components/layout/AppLayout.tsx
// useMenu 훅에서 자동으로 회사별 필터링된 메뉴 가져옴

const { userMenus, adminMenus, loading, refreshMenus } = useMenu();

// 사용자 모드
const currentMenus = isAdminMode ? adminMenus : userMenus;

검증 방법

1. 백엔드 로그 확인

# Docker 로그 확인
docker-compose -f docker/dev/docker-compose.yml logs -f app

# 또는 터미널에서 백엔드 직접 실행
cd backend-node
npm run dev

2. 브라우저 개발자 도구

// 네트워크 탭에서 API 응답 확인
GET /api/admin/menus?menuType=1

// 응답 예시 (COMPANY_ADMIN, ILSHIN)
{
  "success": true,
  "message": "사용자 메뉴 목록 조회 성공",
  "data": [
    {
      "objid": "1",
      "menu_name_kor": "대시보드",
      "company_code": "ILSHIN"  // ✅ ILSHIN 메뉴
    },
    {
      "objid": "2",
      "menu_name_kor": "공통 설정",
      "company_code": null  // ✅ 공통 메뉴
    }
    // ❌ VEXPLOR 메뉴는 없음
  ]
}

3. 데이터베이스 직접 확인

-- 메뉴 데이터 확인
SELECT
  objid,
  menu_name_kor,
  company_code,
  status
FROM menu_info
WHERE menu_type = 1
  AND status = 'active'
ORDER BY seq;

-- 회사별 메뉴 카운트
SELECT
  COALESCE(company_code, '공통') AS company,
  COUNT(*) AS menu_count
FROM menu_info
WHERE status = 'active'
GROUP BY company_code
ORDER BY company;

🚨 주의사항

1. 공통 메뉴 (company_code IS NULL)

  • 모든 회사에서 공통으로 사용하는 메뉴
  • 회사 코드가 NULL인 메뉴는 모든 사용자에게 표시됨

2. 비활성 메뉴 (status='inactive')

  • 회사 코드와 관계없이 표시되지 않음
  • 필터링 전에 status='active' 조건으로 먼저 걸러짐

3. 권한 체크

  • 현재는 메뉴 목록 표시만 필터링
  • 실제 메뉴 접근 권한은 rel_menu_auth 테이블 기반으로 별도 체크 필요
  • 향후 get_user_menus_with_permissions() 함수 활용 가능

🔮 향후 개선 사항

1. 권한 기반 메뉴 필터링

현재는 회사 코드만 체크하지만, 향후 사용자 권한 그룹 기반 필터링 추가:

-- 031_add_menu_auth_columns.sql의 함수 활용
SELECT * FROM get_user_menus_with_permissions('user_id', 'company_code');

2. 캐싱 전략

  • 메뉴 데이터는 자주 변경되지 않으므로 Redis 캐싱 고려
  • 회사별로 캐시 키 분리: menus:company:{companyCode}

3. 다국어 메뉴명

  • 현재는 menu_name_kor 기본 사용
  • MULTI_LANG_TEXT 테이블 기반 다국어 지원 이미 구현됨

📚 참고 자료

  • 데이터베이스 마이그레이션: /db/migrations/031_add_menu_auth_columns.sql
  • 권한 서비스: /backend-node/src/services/roleService.ts
  • 메뉴 관리 컴포넌트: /frontend/components/admin/MenuManagement.tsx

완료 체크리스트

  • 백엔드 컨트롤러 수정 (adminController.ts)
  • 백엔드 서비스 수정 (adminService.ts)
  • 회사별 필터링 로직 구현
  • SUPER_ADMIN 예외 처리
  • 공통 메뉴 필터링 (company_code IS NULL)
  • 비활성 메뉴 제외 (status='active')
  • 로그 메시지 추가
  • 린트 에러 해결
  • 실제 테스트 (각 사용자 유형별)
  • 권한 기반 메뉴 필터링 (향후 개선)

🎉 결론

로그인한 사용자의 회사 코드에 따라 좌측 사이드바 메뉴가 자동으로 필터링되어 표시됩니다.

  • SUPER_ADMIN: 모든 메뉴
  • COMPANY_ADMIN: 자기 회사 + 공통 메뉴
  • 일반 사용자: 자기 회사 + 공통 메뉴

이제 다른 회사의 사용자가 로그인하면 자기 회사에 해당하는 메뉴만 보게 됩니다! 🚀