feat: Complete Phase 1.5 - AuthService Raw Query migration

Phase 1.5 완료: 인증 서비스 Raw Query 전환 및 테스트 완료

 AuthService 전환 완료 (5개 Prisma 호출 제거):
- loginPwdCheck(): Raw Query로 사용자 비밀번호 조회
- insertLoginAccessLog(): Raw Query로 로그인 로그 기록
- getUserInfo(): Raw Query로 사용자/권한/회사 정보 조회
  - authority_sub_user ↔ authority_master JOIN (master_objid ↔ objid)
  - 3개 쿼리로 분리 (사용자, 권한, 회사)
- processLogin(): 전체 로그인 플로우 통합
- processLogout(): 로그아웃 로그 기록

🧪 테스트 완료:
- 단위 테스트: 30개 테스트 모두 통과 
  - 로그인 검증 (6개)
  - 사용자 정보 조회 (5개)
  - 로그인 로그 기록 (4개)
  - 전체 로그인 프로세스 (5개)
  - 로그아웃 (2개)
  - 토큰 검증 (3개)
  - Raw Query 전환 검증 (3개)
  - 성능 테스트 (2개)
- 통합 테스트: 작성 완료 (auth.integration.test.ts)
  - 로그인 → 토큰 발급 → 인증 → 로그아웃 플로우

🔧 주요 변경사항:
- Prisma import 제거 → Raw Query (query from db.ts)
- authority 테이블 JOIN 수정 (auth_code → master_objid/objid)
- 파라미터 바인딩으로 SQL Injection 방지
- 타입 안전성 유지 (TypeScript Generic 사용)

📊 성능:
- 로그인 프로세스: < 1초
- 사용자 정보 조회: < 500ms
- 모든 테스트 실행 시간: 2.016초

🎯 다음 단계:
- Phase 2: 핵심 서비스 전환 (ScreenManagement, TableManagement 등)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
kjs
2025-09-30 15:59:32 +09:00
parent e837ccc1d1
commit 824e5f4827
6 changed files with 902 additions and 104 deletions

View File

@@ -1,7 +1,8 @@
// 인증 서비스
// 기존 Java LoginService를 Node.js로 포팅
// ✅ Prisma → Raw Query 전환 완료 (Phase 1.5)
import prisma from "../config/database";
import { query } from "../database/db";
import { JwtUtils } from "../utils/jwtUtils";
import { EncryptUtil } from "../utils/encryptUtil";
import { PersonBean, LoginResult, LoginLogData } from "../types/auth";
@@ -17,15 +18,13 @@ export class AuthService {
password: string
): Promise<LoginResult> {
try {
// 사용자 비밀번호 조회 (기존 login.getUserPassword 쿼리 포팅)
const userInfo = await prisma.user_info.findUnique({
where: {
user_id: userId,
},
select: {
user_password: true,
},
});
// 사용자 비밀번호 조회 (Raw Query 전환)
const result = await query<{ user_password: string }>(
"SELECT user_password FROM user_info WHERE user_id = $1",
[userId]
);
const userInfo = result.length > 0 ? result[0] : null;
if (userInfo && userInfo.user_password) {
const dbPassword = userInfo.user_password;
@@ -78,32 +77,26 @@ export class AuthService {
*/
static async insertLoginAccessLog(logData: LoginLogData): Promise<void> {
try {
// 기존 login.insertLoginAccessLog 쿼리 포팅
await prisma.$executeRaw`
INSERT INTO LOGIN_ACCESS_LOG(
LOG_TIME,
SYSTEM_NAME,
USER_ID,
LOGIN_RESULT,
ERROR_MESSAGE,
REMOTE_ADDR,
RECPTN_DT,
RECPTN_RSLT_DTL,
RECPTN_RSLT,
RECPTN_RSLT_CD
// 로그인 로그 기록 (Raw Query 전환)
await query(
`INSERT INTO LOGIN_ACCESS_LOG(
LOG_TIME, SYSTEM_NAME, USER_ID, LOGIN_RESULT, ERROR_MESSAGE,
REMOTE_ADDR, RECPTN_DT, RECPTN_RSLT_DTL, RECPTN_RSLT, RECPTN_RSLT_CD
) VALUES (
now(),
${logData.systemName},
UPPER(${logData.userId}),
${logData.loginResult},
${logData.errorMessage || null},
${logData.remoteAddr},
${logData.recptnDt || null},
${logData.recptnRsltDtl || null},
${logData.recptnRslt || null},
${logData.recptnRsltCd || null}
)
`;
now(), $1, UPPER($2), $3, $4, $5, $6, $7, $8, $9
)`,
[
logData.systemName,
logData.userId,
logData.loginResult,
logData.errorMessage || null,
logData.remoteAddr,
logData.recptnDt || null,
logData.recptnRsltDtl || null,
logData.recptnRslt || null,
logData.recptnRsltCd || null,
]
);
logger.info(
`로그인 로그 기록 완료: ${logData.userId} (${logData.loginResult ? "성공" : "실패"})`
@@ -122,66 +115,61 @@ export class AuthService {
*/
static async getUserInfo(userId: string): Promise<PersonBean | null> {
try {
// 기존 login.getUserInfo 쿼리 포팅
const userInfo = await prisma.user_info.findUnique({
where: {
user_id: userId,
},
select: {
sabun: true,
user_id: true,
user_name: true,
user_name_eng: true,
user_name_cn: true,
dept_code: true,
dept_name: true,
position_code: true,
position_name: true,
email: true,
tel: true,
cell_phone: true,
user_type: true,
user_type_name: true,
partner_objid: true,
company_code: true,
locale: true,
photo: true,
},
});
// 1. 사용자 기본 정보 조회 (Raw Query 전환)
const userResult = await query<{
sabun: string | null;
user_id: string;
user_name: string;
user_name_eng: string | null;
user_name_cn: string | null;
dept_code: string | null;
dept_name: string | null;
position_code: string | null;
position_name: string | null;
email: string | null;
tel: string | null;
cell_phone: string | null;
user_type: string | null;
user_type_name: string | null;
partner_objid: string | null;
company_code: string | null;
locale: string | null;
photo: Buffer | null;
}>(
`SELECT
sabun, user_id, user_name, user_name_eng, user_name_cn,
dept_code, dept_name, position_code, position_name,
email, tel, cell_phone, user_type, user_type_name,
partner_objid, company_code, locale, photo
FROM user_info
WHERE user_id = $1`,
[userId]
);
const userInfo = userResult.length > 0 ? userResult[0] : null;
if (!userInfo) {
return null;
}
// 권한 정보 조회 (Prisma ORM 사용)
const authInfo = await prisma.authority_sub_user.findMany({
where: {
user_id: userId,
},
include: {
authority_master: {
select: {
auth_name: true,
},
},
},
});
// 2. 권한 정보 조회 (Raw Query 전환 - JOIN으로 최적화)
const authResult = await query<{ auth_name: string }>(
`SELECT am.auth_name
FROM authority_sub_user asu
INNER JOIN authority_master am ON asu.master_objid = am.objid
WHERE asu.user_id = $1`,
[userId]
);
// 권한명들을 쉼표로 연결
const authNames = authInfo
.filter((auth: any) => auth.authority_master?.auth_name)
.map((auth: any) => auth.authority_master!.auth_name!)
.join(",");
const authNames = authResult.map((row) => row.auth_name).join(",");
// 회사 정보 조회 (Prisma ORM 사용으로 변경)
const companyInfo = await prisma.company_mng.findFirst({
where: {
company_code: userInfo.company_code || "ILSHIN",
},
select: {
company_name: true,
},
});
// 3. 회사 정보 조회 (Raw Query 전환)
// Note: 현재 회사 정보는 PersonBean에 직접 사용되지 않지만 향후 확장을 위해 유지
const companyResult = await query<{ company_name: string }>(
"SELECT company_name FROM company_mng WHERE company_code = $1",
[userInfo.company_code || "ILSHIN"]
);
// DB에서 조회한 원본 사용자 정보 상세 로그
//console.log("🔍 AuthService - DB 원본 사용자 정보:", {