..., 'auth_key' => ..., 'expires_at' => 'Y-m-d H:i:s'] */ public function prepareEmailVerify(int $memNo, string $email, string $ip, int $expiresMinutes = 30): array { return DB::transaction(function () use ($memNo, $email, $ip, $expiresMinutes) { // 1) mem_auth: row lock (없으면 생성, 있으면 N으로 초기화) $row = DB::table('mem_auth') ->where('mem_no', $memNo) ->where('auth_type', 'email') ->lockForUpdate() ->first(); $today = now()->toDateString(); if (!$row) { DB::table('mem_auth')->insert([ 'mem_no' => $memNo, 'auth_type' => 'email', 'auth_state' => 'N', 'auth_date' => $today, ]); } else { DB::table('mem_auth') ->where('mem_no', $memNo) ->where('auth_type', 'email') ->update([ 'auth_state' => 'N', 'auth_date' => $today, ]); } // 2) auth_key 생성 $authKey = now()->format('HisYmd') . $ip . '-' . bin2hex(random_bytes(8)); $expiresAt = now()->addMinutes($expiresMinutes)->format('Y-m-d H:i:s'); $emailInfo = [ 'type' => 'mem_level', 'auth_hit' => 'n', 'user_email' => $email, 'auth_key' => $authKey, 'auth_effective_time' => $expiresAt, 'redate' => now()->format('Y-m-d H:i:s'), ]; // 3) mem_auth_log: P (Pending) DB::table('mem_auth_log')->insert([ 'mem_no' => $memNo, 'type' => 'email', 'state' => 'P', 'info' => json_encode($emailInfo, JSON_UNESCAPED_UNICODE), 'rgdate' => now()->format('Y-m-d H:i:s'), ]); // 4) mem_auth_info upsert (auth_info JSON) $authInfoRow = DB::table('mem_auth_info') ->where('mem_no', $memNo) ->lockForUpdate() ->first(); if (!$authInfoRow) { DB::table('mem_auth_info')->insert([ 'mem_no' => $memNo, 'auth_info' => json_encode(['email' => $emailInfo], JSON_UNESCAPED_UNICODE), ]); } else { $current = json_decode((string)$authInfoRow->auth_info, true) ?: []; $current['email'] = $emailInfo; DB::table('mem_auth_info') ->where('mem_no', $memNo) ->update([ 'auth_info' => json_encode($current, JSON_UNESCAPED_UNICODE), ]); } return [ 'email' => $email, 'auth_key' => $authKey, 'expires_at' => $expiresAt, ]; }); } /** * 인증 완료 처리 (CI3 로직 동일): * - 이미 Y면 예외 * - auth_info.email 존재/키/시간 체크 * - mem_auth: Y 업데이트 * - mem_auth_info: auth_hit=y + remote/agent/시간 merge * - mem_auth_log: S insert */ public function confirmEmailVerify(int $memNo, string $encKey, string $ip, string $agent): array { return DB::transaction(function () use ($memNo, $encKey, $ip, $agent) { // 1) mem_auth 상태 확인 (lock) $auth = DB::table('mem_auth') ->where('mem_no', $memNo) ->where('auth_type', 'email') ->lockForUpdate() ->first(); if ($auth && $auth->auth_state === 'Y') { return ['ok' => false, 'message' => '이미 인증이 완료되었습니다.']; } // 2) mem_auth_info 가져오기 (lock) $infoRow = DB::table('mem_auth_info') ->where('mem_no', $memNo) ->lockForUpdate() ->first(); if (!$infoRow || empty($infoRow->auth_info)) { return ['ok' => false, 'message' => '잘못된 접근입니다.']; } $authJson = json_decode((string)$infoRow->auth_info, true); $emailAuth = $authJson['email'] ?? null; if (!$emailAuth || empty($emailAuth['auth_hit'])) { return ['ok' => false, 'message' => '정상적인 경로로 이용하세요.']; } if (($emailAuth['auth_hit'] ?? '') === 'y') { return ['ok' => false, 'message' => '이미 인증되었습니다.']; } if (($emailAuth['auth_key'] ?? '') !== $encKey) { return ['ok' => false, 'message' => '잘못된 접근입니다.']; } $effective = (string)($emailAuth['auth_effective_time'] ?? ''); if ($effective === '' || $effective < now()->format('Y-m-d H:i:s')) { return ['ok' => false, 'message' => '인증시간이 초과되었습니다.']; } // 3) auth_hit = y + merge info $emailAuth['auth_hit'] = 'y'; $emailAuth = array_merge($emailAuth, [ 'remote_addr' => $ip, 'agent' => $agent, 'auth_redate' => now()->format('Y-m-d H:i:s'), ]); $authJson['email'] = $emailAuth; // 4) mem_auth: Y 업데이트 (없으면 insert) $today = now()->toDateString(); if (!$auth) { DB::table('mem_auth')->insert([ 'mem_no' => $memNo, 'auth_type' => 'email', 'auth_state' => 'Y', 'auth_date' => $today, ]); } else { DB::table('mem_auth') ->where('mem_no', $memNo) ->where('auth_type', 'email') ->update([ 'auth_state' => 'Y', 'auth_date' => $today, ]); } // 5) mem_auth_info 업데이트 DB::table('mem_auth_info') ->where('mem_no', $memNo) ->update([ 'auth_info' => json_encode($authJson, JSON_UNESCAPED_UNICODE), ]); // 6) mem_auth_log: S (Success) $logInfo = [ 'remote_addr' => $ip, 'agent' => $agent, 'auth_redate' => now()->format('Y-m-d H:i:s'), ]; DB::table('mem_auth_log')->insert([ 'mem_no' => $memNo, 'type' => 'email', 'state' => 'S', 'info' => json_encode($logInfo, JSON_UNESCAPED_UNICODE), 'rgdate' => now()->format('Y-m-d H:i:s'), ]); return [ 'ok' => true, 'message' => '이메일 인증이 확인되었습니다.', 'email' => (string)($emailAuth['user_email'] ?? ''), ]; }); } }