본문으로 건너뛰기

주문·SMS 처리 안정성 개선

PHP 8 · GODO26
변경 파일 수: 3개
변경 파일 목록
총 3개
변경 파일 코드
추가삭제
Component/Order/OrderNew.php+12-2
변경 전OrderNew.php
use Component\Payment\Tosspay\TosspayConfig;
use Component\Present\Order\PresentOrder;
use Component\Present\Cart\PresentCart;
 
 
/**
* 주문 class
// 현재 상태 수정 처리
$arrWhere[] = 'sno = ?';
$arrWhere[] = 'orderNo = ?';
 
 
 
 
 
 
foreach ($arrData['sno'] as $key => $val) {
// 주문 로그 저장
$logCode01 = $this->getOrderStatusAdmin($arrData['orderStatus'][$key]) . '(' . $arrData['orderStatus'][$key] . ')';
$orderLogData = $this->db->query_fetch($strSQL, $arrBind, false);
 
if ((int)$orderLogData['cnt'] == 0 || $isClaim === true) {
foreach ($aGoodsSno['sno'] as $v) {
 
 
 
$aValue = array();
 
$aOrderGoodsData = $this->getOnlyOrderGoodsData($orderNo, $v);
 
// 인서트 or 업데이트
$aMemberOrderGoodsCountData = $this->getMemberOrderGoodsCountData($aOrderData['memNo'], $aOrderGoodsData['goodsNo']);
 
변경 후OrderNew.php
use Component\Payment\Tosspay\TosspayConfig;
use Component\Present\Order\PresentOrder;
use Component\Present\Cart\PresentCart;
use Util\Order\OrderGoodsLockSortUtil;
 
/**
* 주문 class
// 현재 상태 수정 처리
$arrWhere[] = 'sno = ?';
$arrWhere[] = 'orderNo = ?';
// [데드락 회피] 같은 주문의 여러 es_orderGoods 행을 트랜잭션마다 동일한 순서(sno 오름차순)로 잠그도록 통일
// 클레임(취소/교환)이 동시 실행될 때 행 잠금 순서가 엇갈려 발생하던 교착(패턴 L)을 예방
// 키를 유지하는 asort 로 정렬하므로 아래 $arrData['orderStatus'][$key] 쌍 대응은 그대로 보존
if (is_array($arrData['sno'])) {
asort($arrData['sno'], SORT_NUMERIC);
}
foreach ($arrData['sno'] as $key => $val) {
// 주문 로그 저장
$logCode01 = $this->getOrderStatusAdmin($arrData['orderStatus'][$key]) . '(' . $arrData['orderStatus'][$key] . ')';
$orderLogData = $this->db->query_fetch($strSQL, $arrBind, false);
 
if ((int)$orderLogData['cnt'] == 0 || $isClaim === true) {
// [데드락 회피] es_memberOrderGoodsCount 잠금 순서 통일을 위해 goodsNo 오름차순으로 처리(상세: OrderGoodsLockSortUtil)
$aResolvedGoodsList = OrderGoodsLockSortUtil::resolveOrderGoodsSortedByGoodsNo($this, $orderNo, $aGoodsSno['sno']);
 
foreach ($aResolvedGoodsList as $aResolved) {
$aValue = array();
 
$aOrderGoodsData = $aResolved['data'];
 
// 인서트 or 업데이트
$aMemberOrderGoodsCountData = $this->getMemberOrderGoodsCountData($aOrderData['memNo'], $aOrderGoodsData['goodsNo']);
 
Component/Order/Order.php+12-2
변경 전Order.php
use Component\RegularDelivery\Notification\RegularDeliveryMailSender;
use Component\RegularDelivery\Notification\RegularDeliverySmsSender;
use Bundle\Component\Payment\Tosspay\TosspayConfig;
 
 
/**
* 주문 class
// 현재 상태 수정 처리
$arrWhere[] = 'sno = ?';
$arrWhere[] = 'orderNo = ?';
 
 
 
 
 
 
foreach ($arrData['sno'] as $key => $val) {
// 주문 로그 저장
$logCode01 = $this->getOrderStatusAdmin($arrData['orderStatus'][$key]) . '(' . $arrData['orderStatus'][$key] . ')';
$orderLogData = $this->db->query_fetch($strSQL, $arrBind, false);
 
if ((int)$orderLogData['cnt'] == 0 || $isClaim === true) {
foreach ($aGoodsSno['sno'] as $v) {
 
 
 
$aValue = array();
 
$aOrderGoodsData = $this->getOnlyOrderGoodsData($orderNo, $v);
 
// 인서트 or 업데이트
$aMemberOrderGoodsCountData = $this->getMemberOrderGoodsCountData($aOrderData['memNo'], $aOrderGoodsData['goodsNo']);
 
변경 후Order.php
use Component\RegularDelivery\Notification\RegularDeliveryMailSender;
use Component\RegularDelivery\Notification\RegularDeliverySmsSender;
use Bundle\Component\Payment\Tosspay\TosspayConfig;
use Util\Order\OrderGoodsLockSortUtil;
 
/**
* 주문 class
// 현재 상태 수정 처리
$arrWhere[] = 'sno = ?';
$arrWhere[] = 'orderNo = ?';
// [데드락 회피] 같은 주문의 여러 es_orderGoods 행을 트랜잭션마다 동일한 순서(sno 오름차순)로 잠그도록 통일
// 클레임(취소/교환)이 동시 실행될 때 행 잠금 순서가 엇갈려 발생하던 교착(패턴 L)을 예방
// 키를 유지하는 asort 로 정렬하므로 아래 $arrData['orderStatus'][$key] 쌍 대응은 그대로 보존
if (is_array($arrData['sno'])) {
asort($arrData['sno'], SORT_NUMERIC);
}
foreach ($arrData['sno'] as $key => $val) {
// 주문 로그 저장
$logCode01 = $this->getOrderStatusAdmin($arrData['orderStatus'][$key]) . '(' . $arrData['orderStatus'][$key] . ')';
$orderLogData = $this->db->query_fetch($strSQL, $arrBind, false);
 
if ((int)$orderLogData['cnt'] == 0 || $isClaim === true) {
// [데드락 회피] es_memberOrderGoodsCount 잠금 순서 통일을 위해 goodsNo 오름차순으로 처리(상세: OrderGoodsLockSortUtil)
$aResolvedGoodsList = OrderGoodsLockSortUtil::resolveOrderGoodsSortedByGoodsNo($this, $orderNo, $aGoodsSno['sno']);
 
foreach ($aResolvedGoodsList as $aResolved) {
$aValue = array();
 
$aOrderGoodsData = $aResolved['data'];
 
// 인서트 or 업데이트
$aMemberOrderGoodsCountData = $this->getMemberOrderGoodsCountData($aOrderData['memNo'], $aOrderGoodsData['goodsNo']);
 
Util/Order/OrderGoodsLockSortUtil.php+59
변경 전OrderGoodsLockSortUtil.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후OrderGoodsLockSortUtil.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Util\Order;
 
/**
* [데드락 회피] 주문상품 잠금 순서 통일용 정렬 유틸
*
* es_memberOrderGoodsCount 는 (memNo, goodsNo) 기준으로 잠기므로, 여러 상품을 갱신할 때
* goodsNo 오름차순으로 처리해 트랜잭션 간 잠금 순서를 통일한다(데드락 패턴 A 예방)
* Order / OrderNew 양쪽에서 동일 로직을 공용으로 사용하기 위해 분리
*/
class OrderGoodsLockSortUtil
{
/**
* 주문상품 sno 목록을 실제 주문상품 정보로 해석한 뒤 goodsNo 오름차순으로 정렬해 반환
*
* sno 입력 순서에 의존하지 않으며, 같은 goodsNo 의 상대 순서는 원본 순서를 보존
* (정렬 안정성 → 중복 goodsNo 누적 처리 동작 불변). goodsNo 를 해석하지 못한 항목은 0 으로 앞에 정렬
*
* @param object $order getOnlyOrderGoodsData(orderNo, sno) 를 제공하는 주문 객체(Order|OrderNew)
* @param string $orderNo 주문번호
* @param array $orderGoodsSnoList 주문상품 sno 목록
*
* @return array 각 항목이 array('seq'=>입력순번, 'goodsNo'=>정렬키, 'data'=>getOnlyOrderGoodsData 결과)
* 이며 goodsNo 오름차순으로 정렬된 목록
*/
public static function resolveOrderGoodsSortedByGoodsNo(object $order, string $orderNo, array $orderGoodsSnoList): array
{
// 재사용 대비 방어: getOnlyOrderGoodsData 를 제공하지 않는 객체면 빈 목록 반환
if (!is_object($order) || !method_exists($order, 'getOnlyOrderGoodsData')) {
return [];
}
 
$resolvedList = [];
foreach ($orderGoodsSnoList as $seq => $orderGoodsSno) {
$orderGoodsData = $order->getOnlyOrderGoodsData($orderNo, $orderGoodsSno);
$resolvedList[] = [
'seq' => $seq,
'goodsNo' => (is_array($orderGoodsData) && isset($orderGoodsData['goodsNo'])) ? (int) $orderGoodsData['goodsNo'] : 0,
'data' => $orderGoodsData,
];
}
usort($resolvedList, function ($leftGoods, $rightGoods) {
if ($leftGoods['goodsNo'] === $rightGoods['goodsNo']) {
return $leftGoods['seq'] - $rightGoods['seq']; // 동률은 원본 순서 유지(정렬 안정성 보장)
}
 
return $leftGoods['goodsNo'] - $rightGoods['goodsNo'];
});
 
return $resolvedList;
}
}