312 lines
8.0 KiB
Markdown
312 lines
8.0 KiB
Markdown
# 메뉴 회사별 필터링 구현 완료
|
|
|
|
## 📋 개요
|
|
|
|
로그인한 사용자의 **회사 코드**에 따라 좌측 사이드바 메뉴가 필터링되어 표시되도록 구현했습니다.
|
|
|
|
## 🎯 구현 내용
|
|
|
|
### 1. 백엔드 수정
|
|
|
|
#### `adminController.ts` 수정
|
|
|
|
- **getAdminMenus()**: 관리자 메뉴 조회 시 사용자 정보 전달
|
|
- **getUserMenus()**: 사용자 메뉴 조회 시 사용자 정보 전달
|
|
|
|
```typescript
|
|
// 추가된 파라미터
|
|
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='*'`)
|
|
|
|
- **모든 회사의 메뉴** 표시
|
|
- 제한 없음
|
|
|
|
```sql
|
|
-- 필터 없음
|
|
WHERE STATUS = 'active'
|
|
```
|
|
|
|
#### COMPANY_ADMIN (`userType='COMPANY_ADMIN'`)
|
|
|
|
- **자기 회사 메뉴 + 공통 메뉴** 표시
|
|
- `company_code = 사용자회사코드` OR `company_code IS NULL`
|
|
|
|
```sql
|
|
-- 회사 필터 적용
|
|
WHERE STATUS = 'active'
|
|
AND (MENU.COMPANY_CODE = '사용자회사코드' OR MENU.COMPANY_CODE IS NULL)
|
|
```
|
|
|
|
#### 일반 사용자 (`userType='USER'`)
|
|
|
|
- **자기 회사 메뉴 + 공통 메뉴** 표시
|
|
- COMPANY_ADMIN과 동일한 필터링
|
|
|
|
```sql
|
|
-- 회사 필터 적용
|
|
WHERE STATUS = 'active'
|
|
AND (MENU.COMPANY_CODE = '사용자회사코드' OR MENU.COMPANY_CODE IS NULL)
|
|
```
|
|
|
|
## 📊 데이터베이스 구조
|
|
|
|
### menu_info 테이블
|
|
|
|
```sql
|
|
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 로그인
|
|
|
|
**기대 결과**: 모든 회사의 메뉴가 표시됨
|
|
|
|
```bash
|
|
# 로그인 사용자: admin (SUPER_ADMIN, company_code='*')
|
|
# 예상 메뉴:
|
|
# - ILSHIN 회사 메뉴
|
|
# - VEXPLOR 회사 메뉴
|
|
# - 공통 메뉴 (company_code IS NULL)
|
|
```
|
|
|
|
### 테스트 2: COMPANY_ADMIN 로그인 (ILSHIN)
|
|
|
|
**기대 결과**: ILSHIN 회사 메뉴 + 공통 메뉴만 표시
|
|
|
|
```bash
|
|
# 로그인 사용자: ilshin_admin (COMPANY_ADMIN, company_code='ILSHIN')
|
|
# 예상 메뉴:
|
|
# - ILSHIN 회사 메뉴
|
|
# - 공통 메뉴 (company_code IS NULL)
|
|
# ❌ VEXPLOR 회사 메뉴 (표시 안 됨)
|
|
```
|
|
|
|
### 테스트 3: COMPANY_ADMIN 로그인 (VEXPLOR)
|
|
|
|
**기대 결과**: VEXPLOR 회사 메뉴 + 공통 메뉴만 표시
|
|
|
|
```bash
|
|
# 로그인 사용자: vexplor_admin (COMPANY_ADMIN, company_code='VEXPLOR')
|
|
# 예상 메뉴:
|
|
# - VEXPLOR 회사 메뉴
|
|
# - 공통 메뉴 (company_code IS NULL)
|
|
# ❌ ILSHIN 회사 메뉴 (표시 안 됨)
|
|
```
|
|
|
|
### 테스트 4: 일반 사용자 로그인
|
|
|
|
**기대 결과**: 자기 회사 메뉴 + 공통 메뉴만 표시
|
|
|
|
```bash
|
|
# 로그인 사용자: user1 (USER, company_code='ILSHIN')
|
|
# 예상 메뉴:
|
|
# - ILSHIN 회사 메뉴 (권한이 있는 메뉴만)
|
|
# - 공통 메뉴 (company_code IS NULL)
|
|
```
|
|
|
|
## 📝 로그 확인
|
|
|
|
백엔드 로그에서 다음과 같은 메시지를 확인할 수 있습니다:
|
|
|
|
```log
|
|
# 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 동작
|
|
|
|
### 좌측 사이드바 메뉴
|
|
|
|
```tsx
|
|
// frontend/components/layout/AppLayout.tsx
|
|
// useMenu 훅에서 자동으로 회사별 필터링된 메뉴 가져옴
|
|
|
|
const { userMenus, adminMenus, loading, refreshMenus } = useMenu();
|
|
|
|
// 사용자 모드
|
|
const currentMenus = isAdminMode ? adminMenus : userMenus;
|
|
```
|
|
|
|
## ✅ 검증 방법
|
|
|
|
### 1. 백엔드 로그 확인
|
|
|
|
```bash
|
|
# Docker 로그 확인
|
|
docker-compose -f docker/dev/docker-compose.yml logs -f app
|
|
|
|
# 또는 터미널에서 백엔드 직접 실행
|
|
cd backend-node
|
|
npm run dev
|
|
```
|
|
|
|
### 2. 브라우저 개발자 도구
|
|
|
|
```javascript
|
|
// 네트워크 탭에서 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. 데이터베이스 직접 확인
|
|
|
|
```sql
|
|
-- 메뉴 데이터 확인
|
|
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. 권한 기반 메뉴 필터링
|
|
|
|
현재는 회사 코드만 체크하지만, 향후 사용자 권한 그룹 기반 필터링 추가:
|
|
|
|
```sql
|
|
-- 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`
|
|
|
|
## ✨ 완료 체크리스트
|
|
|
|
- [x] 백엔드 컨트롤러 수정 (`adminController.ts`)
|
|
- [x] 백엔드 서비스 수정 (`adminService.ts`)
|
|
- [x] 회사별 필터링 로직 구현
|
|
- [x] SUPER_ADMIN 예외 처리
|
|
- [x] 공통 메뉴 필터링 (company_code IS NULL)
|
|
- [x] 비활성 메뉴 제외 (status='active')
|
|
- [x] 로그 메시지 추가
|
|
- [x] 린트 에러 해결
|
|
- [ ] 실제 테스트 (각 사용자 유형별)
|
|
- [ ] 권한 기반 메뉴 필터링 (향후 개선)
|
|
|
|
## 🎉 결론
|
|
|
|
로그인한 사용자의 **회사 코드**에 따라 좌측 사이드바 메뉴가 자동으로 필터링되어 표시됩니다.
|
|
|
|
- **SUPER_ADMIN**: 모든 메뉴
|
|
- **COMPANY_ADMIN**: 자기 회사 + 공통 메뉴
|
|
- **일반 사용자**: 자기 회사 + 공통 메뉴
|
|
|
|
이제 다른 회사의 사용자가 로그인하면 자기 회사에 해당하는 메뉴만 보게 됩니다! 🚀
|