diff --git a/app/Controllers/Admin/AdminController.php b/app/Controllers/Admin/AdminController.php index 9df8015..bd7ddb0 100644 --- a/app/Controllers/Admin/AdminController.php +++ b/app/Controllers/Admin/AdminController.php @@ -4,7 +4,6 @@ namespace App\Controllers\Admin; use App\Controllers\CommonController; -use App\DTOs\CommonDTO; use App\Entities\CommonEntity; use App\Traits\LogTrait; use CodeIgniter\HTTP\RedirectResponse; @@ -27,7 +26,6 @@ abstract class AdminController extends CommonController parent::initController($request, $response, $logger); $this->addActionPaths(self::PATH); } - abstract protected function createDTO(array $formDatas): CommonDTO; final protected function getLayout(): string { return 'admin'; @@ -47,7 +45,7 @@ abstract class AdminController extends CommonController $this->addViewDatas('formRules', $this->service->getFormService()->getFormRules()); $this->addViewDatas('formFilters', $this->service->getFormService()->getFormFilters()); $this->addViewDatas('formOptions', $this->service->getFormService()->getFormOptions()); - $this->addViewDatas('index_actionButtons', $this->service->getFormService()->getActionButtons()); + $this->addViewDatas('index_actionButtons', $this->service->getFormService()->getactionButtons()); $this->addViewDatas('index_batchjobFields', $this->service->getFormService()->getBatchjobFilters()); $this->addViewDatas('index_batchjobButtons', $this->service->getFormService()->getBatchjobButtons()); parent::action_init_process($action); @@ -72,7 +70,7 @@ abstract class AdminController extends CommonController protected function create_process(): CommonEntity { //요청 데이터를 DTO 객체로 변환 - $dto = $this->createDTO($this->request->getPost()); + $dto = $this->service->createDTO($this->request->getPost()); return $this->service->create($dto); } final public function create(): string|RedirectResponse @@ -113,7 +111,7 @@ abstract class AdminController extends CommonController protected function modify_process($uid): CommonEntity { //요청 데이터를 DTO 객체로 변환 - $dto = $this->createDTO($this->request->getPost()); + $dto = $this->service->createDTO($this->request->getPost()); return $this->service->modify($uid, $dto); } final public function modify($uid): string|RedirectResponse @@ -128,46 +126,64 @@ abstract class AdminController extends CommonController return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정 오류:" . $e->getMessage()); } } + protected function batchjob_process($uid, array $formDatas): CommonEntity + { + return $this->service->batchjob($uid, $formDatas); + } final public function batchjob(): string|RedirectResponse { try { - $action = __FUNCTION__; - $this->action_init_process($action); - $selectedFields = []; + $postDatas = $this->request->getPost(); + //1. postDatas에서 선택된 uids 정보 추출 + $uids = []; + if (isset($postDatas['batchjob_uids'])) { + $uids = $postDatas['batchjob_uids']; + unset($postDatas['batchjob_uids']); + } + if (empty($uids)) { + throw new \Exception("적용할 리스트을 선택하셔야합니다."); + } + //2. postDatas에서 필요없는 정보 버림 + unset($postDatas['batchjob_submit']); + //3. 나머지는 $formDatas로 사용할 데이터 추출 $formDatas = []; - foreach ($this->service->getFormService->getBatchjobFilters() as $field) { - $value = $this->request->getPost($field); - if ($value) { - $selectedFields[] = $field; + foreach ($postDatas as $field => $value) { + if ($value !== "") { $formDatas[$field] = $value; } } - if (!count($selectedFields)) { + if (empty($formDatas)) { throw new \Exception("변경할 조건항목을 선택하셔야합니다."); } - //변경할 UIDS 정의 - $uids = $this->request->getPost('batchjob_uids[]'); - if (!is_array($uids) || !count($uids)) { - throw new \Exception("적용할 리스트을 선택하셔야합니다."); - } + //4. 데이터가 있는 필드 추출 + $selectedFields = array_keys($formDatas); + //초기화 $this->service->getFormService()->setFormFields($selectedFields); - $this->service->getFormService()->setFormRules($action, $selectedFields); + $this->service->getFormService()->setFormRules(__FUNCTION__, $selectedFields); $this->service->getFormService()->setFormFilters($selectedFields); $this->service->getFormService()->setFormOptions($selectedFields); $entities = []; - $errors = []; + $error = 0; foreach ($uids as $uid) { try { - $entities[] = $this->modify_process($uid); + $entities[] = $this->batchjob_process($uid, $formDatas); } catch (ValidationException $e) { - $errors[] = "{$this->getTitle()}에서 {$uid} 수정 검증오류:" . $e->getMessage(); + log_message('error', "{$this->getTitle()}에서 {$uid} 수정 검증오류:" . $e->getMessage()); + $error++; } catch (\Exception $e) { - $errors[] = "{$this->getTitle()}에서 {$uid} 수정 오류:" . $e->getMessage(); + log_message('error', "{$this->getTitle()}에서 {$uid} 수정 오류:" . $e->getMessage()); + $error++; } } - return $this->action_modal_process("{$this->getTitle()}에서 " . count($entities) . "개, 총:" . count($uids) . " 수정이 완료되었습니다."); + return $this->action_redirect_process('info', sprintf( + "%s에서 %s개 처리완료, %s개 오류, 총:%s개 수정이 완료되었습니다.", + $this->getTitle(), + count($entities), + $error, + count($uids) + )); } catch (\Exception $e) { - return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정 오류:" . $e->getMessage()); + return $this->action_redirect_process('error', "{$this->getTitle()}에서 일괄작업처리 오류:" . $e->getMessage()); } } protected function delete_process($uid): CommonEntity @@ -183,6 +199,44 @@ abstract class AdminController extends CommonController return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 삭제 오류:" . $e->getMessage()); } } + protected function batchjob_delete_process($uid): CommonEntity + { + return $this->service->batchjob_delete($uid); + } + final public function batchjob_delete(): string|RedirectResponse + { + try { + $postDatas = $this->request->getPost(); + //1. postDatas에서 선택된 uids 정보 추출 + $uids = []; + if (isset($postDatas['batchjob_uids'])) { + $uids = $postDatas['batchjob_uids']; + unset($postDatas['batchjob_uids']); + } + if (empty($uids)) { + throw new \Exception("삭제할 리스트을 선택하셔야합니다."); + } + $entities = []; + $error = 0; + foreach ($uids as $uid) { + try { + $entities[] = $this->batchjob_delete_process($uid); + } catch (\Exception $e) { + log_message('error', "{$this->getTitle()}에서 {$uid} 삭제 오류:" . $e->getMessage()); + $error++; + } + } + return $this->action_redirect_process('info', sprintf( + "%s에서 %s개 처리완료, %s개 오류, 총:%s개 일괄삭제가 완료되었습니다.", + $this->getTitle(), + count($entities), + $error, + count($uids) + )); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 일괄삭제 오류:" . $e->getMessage()); + } + } protected function view_process($uid): CommonEntity { if (!$uid) { diff --git a/app/Controllers/Admin/CollectorController.php b/app/Controllers/Admin/CollectorController.php index 39a7e83..e3ad26f 100644 --- a/app/Controllers/Admin/CollectorController.php +++ b/app/Controllers/Admin/CollectorController.php @@ -2,7 +2,6 @@ namespace App\Controllers\Admin; -use App\DTOs\CollectorDTO; use App\Entities\CollectorEntity; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; @@ -19,14 +18,13 @@ class CollectorController extends AdminController } $this->addActionPaths(self::PATH); } - protected function createDTO(array $formDatas): CollectorDTO - { - return new CollectorDTO($formDatas); - } protected function action_init_process(string $action): void { $fields = ['trafficinfo_uid', 'in', 'out', 'raw_in', 'raw_out']; $filters = ['trafficinfo_uid']; + $batchjobFilters = $filters; + $actionButtons = ['view' => ICONS['SEARCH']]; + $batchjobButtons = []; parent::action_init_process($action); switch ($action) { case 'create': @@ -49,7 +47,9 @@ class CollectorController extends AdminController $this->service->getFormService()->setFormRules($action, $fields); $this->service->getFormService()->setFormFilters($filters); $this->service->getFormService()->setFormOptions($filters); - $this->service->getFormService()->setBatchjobFilters($filters); + $this->service->getFormService()->setBatchjobFilters($batchjobFilters); + $this->service->getFormService()->setActionButtons($actionButtons); + $this->service->getFormService()->setBatchjobButtons($batchjobButtons); parent::action_init_process($action); } protected function create_process(): CollectorEntity @@ -72,4 +72,8 @@ class CollectorController extends AdminController { return parent::view_process($uid); } + protected function batchjob_process($uid, array $formDatas): CollectorEntity + { + return parent::batchjob_process($uid, $formDatas); + } } diff --git a/app/Controllers/Admin/MylogController.php b/app/Controllers/Admin/MylogController.php index e01c9fa..55502bf 100644 --- a/app/Controllers/Admin/MylogController.php +++ b/app/Controllers/Admin/MylogController.php @@ -2,7 +2,6 @@ namespace App\Controllers\Admin; -use App\DTOs\MylogDTO; use App\Entities\MylogEntity; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; @@ -19,14 +18,11 @@ class MylogController extends AdminController } $this->addActionPaths(self::PATH); } - protected function createDTO(array $formDatas): MylogDTO - { - return new MylogDTO($formDatas); - } protected function action_init_process(string $action): void { $fields = ['title', 'content']; $filters = []; + $batchjobFilters = $filters; parent::action_init_process($action); switch ($action) { case 'create': @@ -49,7 +45,7 @@ class MylogController extends AdminController $this->service->getFormService()->setFormRules($action, $fields); $this->service->getFormService()->setFormFilters($filters); $this->service->getFormService()->setFormOptions($filters); - $this->service->getFormService()->setBatchjobFilters($filters); + $this->service->getFormService()->setBatchjobFilters($batchjobFilters); parent::action_init_process($action); } protected function create_process(): MylogEntity @@ -72,4 +68,8 @@ class MylogController extends AdminController { return parent::view_process($uid); } + protected function batchjob_process($uid, array $formDatas): MylogEntity + { + return parent::batchjob_process($uid, $formDatas); + } } diff --git a/app/Controllers/Admin/TrafficController.php b/app/Controllers/Admin/TrafficController.php index 862c3fe..12666df 100644 --- a/app/Controllers/Admin/TrafficController.php +++ b/app/Controllers/Admin/TrafficController.php @@ -2,7 +2,6 @@ namespace App\Controllers\Admin; -use App\DTOs\TrafficDTO; use App\Entities\TrafficEntity; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; @@ -19,14 +18,12 @@ class TrafficController extends AdminController } $this->addActionPaths(self::PATH); } - protected function createDTO(array $formDatas): TrafficDTO - { - return new TrafficDTO($formDatas); - } protected function action_init_process(string $action): void { $fields = ['client', 'server', 'switch', 'server_ip', 'interface', 'ip', 'status']; $filters = ['status']; + $batchjobFilters = $filters; + $actionButtons = ['view' => ICONS['SEARCH'], 'dashboard' => ICONS['CHART'], 'delete' => ICONS['DELETE']]; switch ($action) { case 'create': case 'create_form': @@ -48,7 +45,8 @@ class TrafficController extends AdminController $this->service->getFormService()->setFormRules($action, $fields); $this->service->getFormService()->setFormFilters($filters); $this->service->getFormService()->setFormOptions($filters); - $this->service->getFormService()->setBatchjobFilters($filters); + $this->service->getFormService()->setBatchjobFilters($batchjobFilters); + $this->service->getFormService()->setActionButtons($actionButtons); parent::action_init_process($action); } protected function create_process(): TrafficEntity @@ -71,6 +69,10 @@ class TrafficController extends AdminController { return parent::view_process($uid); } + protected function batchjob_process($uid, array $formDatas): TrafficEntity + { + return parent::batchjob_process($uid, $formDatas); + } public function dashboard($uid): string { $entity = $this->service->getEntity($uid); diff --git a/app/Controllers/Admin/UserController.php b/app/Controllers/Admin/UserController.php index 6fdde5b..c172730 100644 --- a/app/Controllers/Admin/UserController.php +++ b/app/Controllers/Admin/UserController.php @@ -2,7 +2,6 @@ namespace App\Controllers\Admin; -use App\DTOs\UserDTO; use App\Entities\UserEntity; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; @@ -19,15 +18,12 @@ class UserController extends AdminController } $this->addActionPaths(self::PATH); } - protected function createDTO(array $formDatas): UserDTO - { - return new UserDTO($formDatas); - } //Action작업관련 protected function action_init_process(string $action): void { $fields = ['id', 'passwd', 'confirmpassword', 'name', 'email', 'mobile', 'role']; $filters = ['role', 'status']; + $batchjobFilters = ['status']; switch ($action) { case 'create': case 'create_form': @@ -49,7 +45,7 @@ class UserController extends AdminController $this->service->getFormService()->setFormRules($action, $fields); $this->service->getFormService()->setFormFilters($filters); $this->service->getFormService()->setFormOptions($filters); - $this->service->getFormService()->setBatchjobFilters($filters); + $this->service->getFormService()->setBatchjobFilters($batchjobFilters); parent::action_init_process($action); } protected function create_form_process(array $formDatas = []): array @@ -77,4 +73,8 @@ class UserController extends AdminController { return parent::view_process($uid); } + protected function batchjob_process($uid, array $formDatas): UserEntity + { + return parent::batchjob_process($uid, $formDatas); + } } diff --git a/app/DTOs/Auth/AuthDTO.php b/app/DTOs/Auth/AuthDTO.php index c3fc649..7d9c2c4 100644 --- a/app/DTOs/Auth/AuthDTO.php +++ b/app/DTOs/Auth/AuthDTO.php @@ -4,10 +4,11 @@ namespace App\DTOs\Auth; use App\DTOs\CommonDTO; -class AuthDTO extends CommonDTO +abstract class AuthDTO extends CommonDTO { public function __construct() { parent::__construct(); } + abstract public function toArray(); } diff --git a/app/Forms/CollectorForm.php b/app/Forms/CollectorForm.php index 58c26b9..1e42ed3 100644 --- a/app/Forms/CollectorForm.php +++ b/app/Forms/CollectorForm.php @@ -9,7 +9,6 @@ class CollectorForm extends CommonForm public function __construct() { parent::__construct(); - $this->setActionButtons(['view' => ICONS['SEARCH']]); } public function getFormRule(string $action, string $field): string diff --git a/app/Forms/CommonForm.php b/app/Forms/CommonForm.php index 2576909..c5fa5d9 100644 --- a/app/Forms/CommonForm.php +++ b/app/Forms/CommonForm.php @@ -36,39 +36,32 @@ abstract class CommonForm //$fields 매치된것만 반환, []->전체 final public function getFormFields(array $fields = []): array { - // 1. $fields 배열이 비어있는지 확인합니다. - // getFormFields('create') 와 같이 호출될 경우 $fields는 빈 배열[]이 됩니다. if (empty($fields)) { - // 비어있다면, _formFields 전체를 반환합니다. return $this->_formFields; } - // $fields의 값(원하는 필드명)을 키로 변환합니다. - // 예: ['test1'] -> ['test1' => 0] - $fieldsAsKeys = array_flip($fields); - // _formFields와 $fieldsAsKeys 간의 키를 비교하여 교집합을 반환합니다. - // 즉, $fields에 지정된 필드 정의만 추출됩니다. - return array_intersect_key($this->_formFields, $fieldsAsKeys); + // _formFields와 키를 비교하여 교집합을 반환합니다. $fields에 지정된 필드 정의만 추출됩니다. + return array_intersect_key($this->_formFields, array_flip($fields)); } - final public function setFormRules(string $action, array $fields): void + final public function setFormRules(string $action, array $fields, $formRules = []): void { foreach ($fields as $field) { - $this->_formRules[$field] = $this->getFormRule($action, $field); + $formRules[$field] = $formRules[$field] ?? $this->getFormRule($action, $field); } + $this->_formRules = $formRules; } - //$fields 매치된것만 반환, []->전체 final public function getFormRules(array $fields = []): array { if (empty($fields)) { return $this->_formRules; } - $fieldsAsKeys = array_flip($fields); - return array_intersect_key($this->_formRules, $fieldsAsKeys); + return array_intersect_key($this->_formRules, array_flip($fields)); } - final public function setFormOptions(array $fields): void + final public function setFormOptions(array $fields, $formOptions = []): void { foreach ($fields as $field) { - $this->_formOptions[$field] = $this->getFormOption($field); + $formOptions[$field] = $formOptions[$field] ?? $this->getFormOption($field); } + $this->_formOptions = $formOptions; // dd($this->_formOptions[$field]); } //$fields 매치된것만 반환, []->전체 @@ -77,8 +70,7 @@ abstract class CommonForm if (empty($fields)) { return $this->_formOptions; } - $fieldsAsKeys = array_flip($fields); - return array_intersect_key($this->_formOptions, $fieldsAsKeys); + return array_intersect_key($this->_formOptions, array_flip($fields)); } final public function setFormFilters(array $fields): void { @@ -96,17 +88,17 @@ abstract class CommonForm { return $this->_batchjobFilters; } - final public function setActionButtons(array $buttions): array + final public function setActionButtons(array $buttons): array { - return $this->_actionButtons = $buttions; + return $this->_actionButtons = $buttons; } - final public function getActionButtons(): array + final public function getactionButtons(): array { return $this->_actionButtons; } - final public function setBatchjobButtons(array $buttions): array + final public function setBatchjobButtons(array $buttons): array { - return $this->_batchjobButtons = $buttions; + return $this->_batchjobButtons = $buttons; } final public function getBatchjobButtons(): array { diff --git a/app/Forms/TrafficForm.php b/app/Forms/TrafficForm.php index dccdcac..c5282bc 100644 --- a/app/Forms/TrafficForm.php +++ b/app/Forms/TrafficForm.php @@ -9,7 +9,6 @@ class TrafficForm extends CommonForm public function __construct() { parent::__construct(); - $this->setActionButtons(['view' => ICONS['SEARCH'], 'dashboard' => ICONS['CHART'], 'delete' => ICONS['DELETE']]); } public function getFormRule(string $action, string $field): string { diff --git a/app/Libraries/SNMPLibrary.php b/app/Libraries/SNMPLibrary.php new file mode 100644 index 0000000..78ed7fd --- /dev/null +++ b/app/Libraries/SNMPLibrary.php @@ -0,0 +1,37 @@ +_ip = $ip; + $this->_community = $community; + } + + public function get(string $fullOid): ?int + { + // snmp2_get을 사용하여 SNMP v2c로 요청 + // 💡 snmp2_get() 함수가 존재하지 않는다는 LSP 오류를 무시하기 위해 @suppress 태그 사용 + /** @phpstan-ignore-next-line */ + $result = @snmp2_get($this->_ip, $this->_community, $fullOid, 100000, 3); + + if ($result === false || $result === null) { + log_message('error', "SNMP 통신 실패: {$this->_ip} ({$fullOid}, Community: {$this->_community})"); + return null; + } + + // 💡 정규식 수정: /\d+$/ (문자열 끝의 숫자만 추출) + if (preg_match('/\d+$/', $result, $matches)) { + // SNMP Counter는 64비트 BigInt가 될 수 있으므로, 64비트 PHP 환경에서는 + // (int) 캐스팅을 사용하여 안전하게 64비트 정수로 변환합니다. + // (BigInt 값을 DB에 저장할 때도 PHP의 정수형/문자열 처리 능력이 중요합니다.) + return (int)$matches[0]; + } + return null; + } +} diff --git a/app/Services/Auth/AuthService.php b/app/Services/Auth/AuthService.php index ab07f6f..0f0fb39 100644 --- a/app/Services/Auth/AuthService.php +++ b/app/Services/Auth/AuthService.php @@ -3,6 +3,7 @@ namespace App\Services\Auth; use App\DTOs\Auth\AuthDTO; +use App\Entities\CommonEntity; use App\Entities\UserEntity; use App\Helpers\AuthHelper; use App\Libraries\AuthContext; @@ -56,10 +57,7 @@ abstract class AuthService { return $isArray ? $this->_classPaths : implode($delimeter, $this->_classPaths); } - protected function getEntity_process(mixed $entity): mixed - { - return $entity; - } + abstract protected function getEntity_process(mixed $entity): CommonEntity; final public function getEntity(string|int|array $where, ?string $message = null): mixed { try { @@ -100,7 +98,7 @@ abstract class AuthService abstract protected function login_process(array $formDatas): UserEntity; public function login(AuthDTO $dto): UserEntity { - $formDatas = (array)$dto; + $formDatas = $dto->toArray(); // dd($formDatas); //입력값 검증 if (!$this->getFormService()->validate($formDatas)) { diff --git a/app/Services/Auth/GoogleService.php b/app/Services/Auth/GoogleService.php index 629d756..24ce3bd 100644 --- a/app/Services/Auth/GoogleService.php +++ b/app/Services/Auth/GoogleService.php @@ -33,6 +33,10 @@ class GoogleService extends AuthService } return $this->_form; } + protected function getEntity_process(mixed $entity): UserEntity + { + return $entity; + } //기본기능 protected function login_process(array $formDatas): UserEntity { diff --git a/app/Services/Auth/LocalService.php b/app/Services/Auth/LocalService.php index 76497b2..e94b451 100644 --- a/app/Services/Auth/LocalService.php +++ b/app/Services/Auth/LocalService.php @@ -33,6 +33,10 @@ class LocalService extends AuthService } return $this->_form; } + protected function getEntity_process(mixed $entity): UserEntity + { + return $entity; + } protected function login_process(array $formDatas): UserEntity { $entity = $this->getEntity(['id' => $formDatas['id'], 'status' => 'AVAILABLE'], true); diff --git a/app/Services/CollectorService.php b/app/Services/CollectorService.php index 8d6bb10..880ed12 100644 --- a/app/Services/CollectorService.php +++ b/app/Services/CollectorService.php @@ -7,30 +7,27 @@ use App\Entities\CollectorEntity; use App\Entities\TrafficEntity; use App\Forms\CollectorForm; use App\Helpers\CollectorHelper; +use App\Libraries\SNMPLibrary; use App\Models\CollectorModel; use CodeIgniter\I18n\Time; use RuntimeException; class CollectorService extends CommonService { + const OID_IF_IN_OCTETS = '1.3.6.1.2.1.31.1.1.1.6.'; + const OID_IF_OUT_OCTETS = '1.3.6.1.2.1.31.1.1.1.10.'; private $_form = null; private $_helper = null; - // 💡 64비트 카운터를 위해 High Capacity OID로 변경: ifHCInOctets - const OID_IF_IN_OCTETS = '1.3.6.1.2.1.31.1.1.1.6.'; - // 💡 64비트 카운터를 위해 High Capacity OID로 변경: ifHCOutOctets - const OID_IF_OUT_OCTETS = '1.3.6.1.2.1.31.1.1.1.10.'; - - const SNMP_VERSION = '2c'; - - // 💡 32비트 롤오버 로직을 제거했으므로 해당 상수는 필요 없습니다. - // const MAX_COUNTER_32BIT = 4294967296; public function __construct(CollectorModel $model) { parent::__construct($model); $this->addClassPaths('Collector'); } - + public function createDTO(array $formDatas): CollectorDTO + { + return new CollectorDTO($formDatas); + } public function getFormService(): CollectorForm { if ($this->_form === null) { @@ -60,8 +57,15 @@ class CollectorService extends CommonService } return $this->_helper; } - //기본 기능부분 + protected function getEntity_process(mixed $entity): CollectorEntity + { + return $entity; + } + protected function create_process(array $formDatas): CollectorEntity + { + return new CollectorEntity($formDatas); + } public function create(object $dto): CollectorEntity { if (!$dto instanceof CollectorDTO) { @@ -69,7 +73,10 @@ class CollectorService extends CommonService } return parent::create($dto); } - + protected function modify_process($uid, array $formDatas): CollectorEntity + { + return parent::modify_process($uid, $formDatas); + } public function modify($uid, object $dto): CollectorEntity { if (!$dto instanceof CollectorDTO) { @@ -97,51 +104,23 @@ class CollectorService extends CommonService return $entities; } - //SNMP연결 - private function getSNMPOctets(TrafficEntity $trafficEntity, string $oid): ?int - { - $fullOid = $oid . $trafficEntity->getInterface(); - $community = $trafficEntity->getCommunity(); - $ip = $trafficEntity->getIP(); - // snmp2_get을 사용하여 SNMP v2c로 요청 - // 💡 snmp2_get() 함수가 존재하지 않는다는 LSP 오류를 무시하기 위해 @suppress 태그 사용 - /** @phpstan-ignore-next-line */ - $result = @snmp2_get($ip, $community, $fullOid, 100000, 3); - - if ($result === false || $result === null) { - log_message('error', "SNMP 통신 실패: {$ip} ({$fullOid}, Community: {$community})"); - return null; - } - - // 💡 정규식 수정: /\d+$/ (문자열 끝의 숫자만 추출) - if (preg_match('/\d+$/', $result, $matches)) { - // SNMP Counter는 64비트 BigInt가 될 수 있으므로, 64비트 PHP 환경에서는 - // (int) 캐스팅을 사용하여 안전하게 64비트 정수로 변환합니다. - // (BigInt 값을 DB에 저장할 때도 PHP의 정수형/문자열 처리 능력이 중요합니다.) - return (int)$matches[0]; - } - return null; - } public function getCalculatedData(TrafficEntity $trafficEntity): array { - $currentInOctets = $this->getSNMPOctets($trafficEntity, self::OID_IF_IN_OCTETS); - $currentOutOctets = $this->getSNMPOctets($trafficEntity, self::OID_IF_OUT_OCTETS); + $snmp = new SNMPLibrary($trafficEntity->getIP(), $trafficEntity->getCommunity()); + $currentInOctets = $snmp->get(self::OID_IF_IN_OCTETS . $trafficEntity->getInterface()); + $currentOutOctets = $snmp->get(self::OID_IF_OUT_OCTETS . $trafficEntity->getInterface()); if ($currentInOctets === null || $currentOutOctets === null) { $message = "트래픽 수집 실패: {$trafficEntity->getIP()} - IF{$trafficEntity->getInterface()} (UID: {$trafficEntity->getPK()})"; - log_message('warning', $message); + log_message('error', $message); throw new \Exception($message); } - // 이전 데이터를 조회하여 Rate 계산에 사용 - // $this->model은 TrafficDataModel의 인스턴스라고 가정 $lastEntity = $this->model->getLastEntity($trafficEntity->getPK()); - $inKbitsSec = 0.0; $outKbitsSec = 0.0; - // 이전 데이터가 있어야만 Rate 계산 가능 if ($lastEntity !== null) { $lastTime = Time::parse($lastEntity->getCreatedAt())->getTimestamp(); @@ -153,28 +132,18 @@ class CollectorService extends CommonService $lastIn = $lastEntity->getRawIn(); $lastOut = $lastEntity->getRawOut(); - // 💡 1. 인바운드 Octets 차분 계산 (32비트 롤오버 로직 제거) - // 64비트 카운터(BIGINT)를 사용하기 때문에 단순 뺄셈으로 처리합니다. - // 64비트 카운터는 실질적으로 롤오버될 일이 없습니다. + // 💡 1. IN/OUT바운드 Octets 차분 계산 64비트 카운터(BIGINT)를 사용하기 때문에 단순 뺄셈으로 처리합니다. $deltaInOctets = $currentInOctets - $lastIn; - - // 💡 2. 아웃바운드 Octets 차분 계산 (32비트 롤오버 로직 제거) - // 64비트 카운터(BIGINT)를 사용하기 때문에 단순 뺄셈으로 처리합니다. $deltaOutOctets = $currentOutOctets - $lastOut; - // Kbit/s 계산: (Delta_Octets * 8 bits) / Delta_Time_Seconds / 1000 (-> Kbit/s) - // 실수(float) 연산으로 정확도를 높입니다. + // Kbit/s 계산: (Delta_Octets * 8 bits) / Delta_Time_Seconds / 1000 (-> Kbit/s) 실수(float) 연산으로 정확도를 높입니다. $inKbitsSec = ($deltaInOctets * 8.0) / $deltaTime / 1000.0; $outKbitsSec = ($deltaOutOctets * 8.0) / $deltaTime / 1000.0; } else { log_message('error', "시간 차이 오류 발생: {$trafficEntity->getIP()} - {$deltaTime}초 (UID: {$trafficEntity->getPK()})"); } } - - // DB에 저장할 데이터를 배열로 반환 return [ - // 'raw_in'과 'raw_out'은 이제 64비트 정수(BIGINT)를 담습니다. - // PHP에서 (int) 캐스팅은 64비트 환경에서 64비트 정수를 의미합니다. 'trafficinfo_uid' => (int)$trafficEntity->getPK(), 'in' => (int)$inKbitsSec, // 정수형으로 반환 'out' => (int)$outKbitsSec, // 정수형으로 반환 diff --git a/app/Services/CommonService.php b/app/Services/CommonService.php index 32f7a94..5951ea0 100644 --- a/app/Services/CommonService.php +++ b/app/Services/CommonService.php @@ -2,6 +2,7 @@ namespace App\Services; +use App\DTOs\CommonDTO; use App\Entities\CommonEntity; use App\Models\CommonModel; use CodeIgniter\Database\Exceptions\DatabaseException; @@ -13,6 +14,7 @@ abstract class CommonService private array $_classPaths = []; protected $title = null; protected function __construct(protected CommonModel $model) {} + abstract public function createDTO(array $formDatas): CommonDTO; abstract public function getFormService(): mixed; abstract public function getHelper(): mixed; final protected function addClassPaths(string $path): void @@ -24,18 +26,22 @@ abstract class CommonService return $isArray ? $this->_classPaths : implode($delimeter, $this->_classPaths); } - final public function getEntity(string|int|array $where, ?string $message = null): mixed + /** + * 단일 엔티티를 조회합니다. + * @return CommonEntity|null CommonEntity 인스턴스 또는 찾지 못했을 경우 null + */ + final public function getEntity(string|int|array $where, ?string $message = null): ?CommonEntity { try { $entity = is_array($where) ? $this->model->where($where)->first() : $this->model->find($where); if (!$entity) { return null; } - if (is_array($entity)) { - throw new \Exception(__METHOD__ . "에서 결과값 Array 오류발생:\n" . var_export($entity, true)); + if (!$entity instanceof CommonEntity) { + throw new \Exception(__METHOD__ . "에서 결과값 오류발생:\n" . var_export($entity, true)); } return $this->getEntity_process($entity); - } catch (DatabaseException $e) { // 💡 DB 오류를 명시적으로 잡음 + } catch (DatabaseException $e) { $errorMessage = sprintf( "\n------DB Query 오류 (%s)-----\nQuery: %s\nError: %s\n------------------------------\n", __FUNCTION__, @@ -44,7 +50,7 @@ abstract class CommonService ); log_message('error', $errorMessage); throw new RuntimeException($errorMessage, $e->getCode(), $e); - } catch (\Exception $e) { // 기타 일반적인 예외 처리 + } catch (\Exception $e) { $errorMessage = sprintf( "\n------일반 오류 (%s)-----\nError: %s\n------------------------------\n", __FUNCTION__, @@ -59,7 +65,7 @@ abstract class CommonService $entities = $this->getEntities_process($where, $columns); log_message('debug', $this->model->getLastQuery()); return $entities; - } catch (DatabaseException $e) { // 💡 DB 오류를 명시적으로 잡음 + } catch (DatabaseException $e) { $errorMessage = sprintf( "\n------DB Query 오류 (%s)-----\nQuery: %s\nError: %s\n------------------------------\n", __FUNCTION__, @@ -68,7 +74,7 @@ abstract class CommonService ); log_message('error', $errorMessage); throw new RuntimeException($errorMessage, $e->getCode(), $e); - } catch (\Exception $e) { // 기타 일반적인 예외 처리 + } catch (\Exception $e) { $errorMessage = sprintf( "\n------일반 오류 (%s)-----\nError: %s\n------------------------------\n", __FUNCTION__, @@ -76,19 +82,17 @@ abstract class CommonService ); throw new \Exception($errorMessage, $e->getCode(), $e); } - } // + } - final public function getLatestPK(): int + final public function getNextPK(): int { $pkField = $this->model->getPKField(); - // $this->model->selectMax($pkField)->get()->getRow() 대신 row() 사용 권장 $row = $this->model->selectMax($pkField)->get()->getRow(); - // uid 대신 PK 필드명을 동적으로 사용 return isset($row->{$pkField}) ? ((int)$row->{$pkField} + 1) : 1; } //Entity관련 - protected function getEntity_process(mixed $entity): mixed + protected function getEntity_process(CommonEntity $entity): CommonEntity { return $entity; } @@ -99,11 +103,7 @@ abstract class CommonService if ($where) { $this->model->where($where); } - //출력순서 정의 - $this->setOrderBy(); $entities = []; - - // findAll()이 DB 오류 없이 실행되었다면 문제 없음 foreach ($this->model->select(implode(',', $columns))->findAll() as $entity) { $entities[$entity->getPK()] = $this->getEntity_process($entity); } @@ -111,100 +111,148 @@ abstract class CommonService } //CURD 결과처리용 - //DB 결과 처리 로직 통합 및 개선 - protected function handle_save_result(mixed $result, ?int $uid = null): CommonEntity + protected function handle_save_result(mixed $result, int|string $uid): int|string { - //최종 PK 값 결정 (insert/update 공통) - $pk = $this->model->useAutoIncrement() && is_numeric($result) && (int)$result > 0 ? (int)$result : $uid; - if (empty($pk)) { - // 모델의 insert/update가 실패했을 경우 에러 메시지를 포함하여 RuntimeException을 던집니다. - $errors = $this->model->errors(); - $errorMsg = is_array($errors) && !empty($errors) ? implode(", ", $errors) : "DB 작업 성공 후 PK를 확인할 수 없거나 모델 오류 발생."; - throw new RuntimeException(__METHOD__ . "에서 오류발생: " . $errorMsg); - } - $entity = $this->getEntity($pk); - if (!$entity instanceof CommonEntity) { - throw new RuntimeException(__METHOD__ . "에서 오류발생: {$pk}에 해당하는 정보를 찾을수 없습니다."); - } - return $entity; - } - - //생성용 - protected function create_process(array $formDatas): CommonEntity - { - $result = $this->model->insert($formDatas, $this->model->useAutoIncrement()); log_message('debug', $this->model->getLastQuery()); if ($result === false) { $errors = $this->model->errors(); $errorMsg = is_array($errors) ? implode(", ", $errors) : "삽입 작업이 실패했습니다."; throw new RuntimeException(__METHOD__ . "에서 오류발생: " . $errorMsg); } - return $this->handle_save_result($result); + // $pk는 auto-increment가 사용된 경우 새로 생성된 ID, 아니면 기존 $uid (업데이트의 경우) + $pk = $this->model->useAutoIncrement() && is_numeric($result) && (int)$result > 0 ? (int)$result : $uid; + + if (empty($pk)) { + $errors = $this->model->errors(); + $errorMsg = is_array($errors) && !empty($errors) ? implode(", ", $errors) : "DB 작업 성공 후 PK를 확인할 수 없거나 모델 오류 발생:{$pk}"; + throw new RuntimeException(__METHOD__ . "에서 오류발생: " . $errorMsg); + } + return $pk; } + protected function save_process(CommonEntity $entity): CommonEntity + { + $result = $this->model->save($entity); + // 최종적으로 DB에 반영된 PK를 반환받습니다. (UPDATE이면 기존 PK, INSERT이면 새 PK) + $finalPK = $this->handle_save_result($result, $entity->getPK()); + return $this->getEntity($finalPK); + } + + //생성용 + abstract protected function create_process(array $formDatas): CommonEntity; public function create(object $dto): CommonEntity { - $formDatas = (array)$dto; - //입력값 검증 + $formDatas = $dto->toArray(); if (!$this->getFormService()->validate($formDatas)) { throw new ValidationException(implode("\n", service('validation')->getErrors())); } - return $this->create_process($formDatas); + $entity = $this->create_process($formDatas); + return $this->save_process($entity); } //수정용 - protected function modify_process($uid, array $formDatas): CommonEntity + protected function modify_process(string|int $uid, array $formDatas): CommonEntity { - if (!$uid) { - throw new \Exception("수정에 필요한 PrimaryKey 가 정의 되지 않았습니다."); - } $entity = $this->getEntity($uid); - if (!$entity instanceof CommonEntity) { + if (!$entity) { throw new \Exception(__METHOD__ . "에서 오류발생: {$uid}에 해당하는 정보을 찾을수 없습니다."); } - //PrimaryKey 필드는 수정에서 제외 - unset($formDatas[$this->model->primaryKey]); - $result = $this->model->update($uid, $formDatas); - log_message('debug', $this->model->getLastQuery()); - if ($result === false) { - $errors = $this->model->errors(); - $errorMsg = is_array($errors) ? implode(", ", $errors) : "업데이트 작업이 실패했습니다."; - throw new RuntimeException(__METHOD__ . "에서 오류발생: " . $errorMsg); + + $pkField = $this->model->getPKField(); + + // DTO에서 넘어온 데이터에 PK 필드가 포함되어 있으면 제거하여, + // 기존 엔티티의 PK를 덮어쓰지 못하도록 방어 + if (isset($formDatas[$pkField])) { + unset($formDatas[$pkField]); } - return $this->handle_save_result($result, $uid); + + $entity->fill($formDatas); + + // <<< FIX: fill() 후 PK가 유실되었는지 확인하고 강제로 재설정 (방어적 코딩) >>> + if (empty($entity->getPK())) { + log_message('warning', "modify_process에서 fill() 후 PK가 유실되어 uid {$uid}를 강제로 재설정합니다."); + // Entity의 PK 필드를 직접 설정하여 UPDATE가 실행되도록 보장 + $entity->{$pkField} = $uid; + } + + // save_process 진입 전 Entity의 PK 최종 확인 로그 + log_message('debug', "save_process 진입 전 Entity PK: " . $entity->getPK() . " (기대값: {$uid})"); + + return $entity; } - public function modify($uid, object $dto): CommonEntity + public function modify(string|int $uid, object $dto): CommonEntity { - $formDatas = (array)$dto; - //입력값 검증 + $formDatas = $dto->toArray(); if (!$this->getFormService()->validate($formDatas)) { throw new ValidationException(implode("\n", service('validation')->getErrors())); } - return $this->modify_process($uid, $formDatas); + $entity = $this->modify_process($uid, $formDatas); + return $this->save_process($entity); } - protected function delete_process($uid): CommonEntity + + //배치 작업용 수정 + protected function batchjob_process(string|int $uid, array $formDatas): CommonEntity + { + // modify_process를 호출하여 로직 재사용 (PK 로드 및 PK 제거/방어 로직 포함) + $entity = $this->modify_process($uid, $formDatas); + return $entity; + } + public function batchjob(string|int $uid, array $formDatas): CommonEntity + { + if (!$this->getFormService()->validate($formDatas)) { + throw new ValidationException(implode("\n", service('validation')->getErrors())); + } + $entity = $this->batchjob_process($uid, $formDatas); + return $this->save_process($entity); + } + + //삭제용 (일반) + protected function delete_process(string|int $uid): CommonEntity { if (!$uid) { throw new \Exception("삭제에 필요한 PrimaryKey 가 정의 되지 않았습니다."); } $entity = $this->getEntity($uid); - if (!$entity instanceof CommonEntity) { + if (!$entity) { throw new \Exception(__METHOD__ . "에서 오류발생: {$uid}에 해당하는 정보을 찾을수 없습니다."); } return $entity; } - final public function delete($uid): CommonEntity + final public function delete(string|int $uid): CommonEntity { $entity = $this->delete_process($uid); $result = $this->model->delete($entity->getPK()); log_message('debug', $this->model->getLastQuery()); if ($result === false) { $errors = $this->model->errors(); - $errorMsg = is_array($errors) ? implode(", ", $errors) : "모델 삭제 작업이 실패했습니다."; + $errorMsg = is_array($errors) ? implode(", ", $errors) : "삭제 작업이 실패했습니다."; throw new RuntimeException(__METHOD__ . "에서 오류발생: " . $errorMsg); } return $entity; } + + //삭제용 (배치 작업) + protected function batchjob_delete_process(string|int $uid): CommonEntity + { + // delete_process를 호출하여 로직 재사용 (CommonEntity 로드 및 유효성 검사) + $entity = $this->delete_process($uid); + return $entity; + } + + final public function batchjob_delete(string|int $uid): CommonEntity + { + $entity = $this->batchjob_delete_process($uid); + + $result = $this->model->delete($entity->getPK()); + log_message('debug', $this->model->getLastQuery()); + if ($result === false) { + $errors = $this->model->errors(); + $errorMsg = is_array($errors) ? implode(", ", $errors) : "삭제 작업이 실패했습니다."; + throw new RuntimeException(__METHOD__ . "에서 오류발생: " . $errorMsg); + } + return $entity; + } + //Index용 final public function getTotalCount(): int { diff --git a/app/Services/MylogService.php b/app/Services/MylogService.php index 0f10510..78d4e31 100644 --- a/app/Services/MylogService.php +++ b/app/Services/MylogService.php @@ -18,6 +18,10 @@ class MylogService extends CommonService parent::__construct($model); $this->addClassPaths('Mylog'); } + public function createDTO(array $formDatas): MylogDTO + { + return new MylogDTO($formDatas); + } public function getFormService(): MylogForm { if ($this->_form === null) { @@ -47,6 +51,14 @@ class MylogService extends CommonService return $this->_helper; } //기본 기능부분 + protected function getEntity_process(mixed $entity): MylogEntity + { + return $entity; + } + protected function create_process(array $formDatas): MylogEntity + { + return new MylogEntity($formDatas); + } public function create(object $dto): MylogEntity { if (!$dto instanceof MylogDTO) { @@ -54,6 +66,10 @@ class MylogService extends CommonService } return parent::create($dto); } + protected function modify_process($uid, array $formDatas): MyLogEntity + { + return parent::modify_process($uid, $formDatas); + } public function modify($uid, object $dto): MyLogEntity { if (!$dto instanceof MylogDTO) { diff --git a/app/Services/TrafficService.php b/app/Services/TrafficService.php index 2904004..2b16523 100644 --- a/app/Services/TrafficService.php +++ b/app/Services/TrafficService.php @@ -18,6 +18,10 @@ class TrafficService extends CommonService parent::__construct($model); $this->addClassPaths('Traffic'); } + public function createDTO(array $formDatas): TrafficDTO + { + return new TrafficDTO($formDatas); + } public function getFormService(): TrafficForm { if ($this->_form === null) { @@ -47,6 +51,14 @@ class TrafficService extends CommonService return $this->_helper; } //기본 기능부분 + protected function getEntity_process(mixed $entity): TrafficEntity + { + return $entity; + } + protected function create_process(array $formDatas): TrafficEntity + { + return new TrafficEntity($formDatas); + } public function create(object $dto): TrafficEntity { if (!$dto instanceof TrafficDTO) { @@ -54,6 +66,10 @@ class TrafficService extends CommonService } return parent::create($dto); } + protected function modify_process($uid, array $formDatas): TrafficEntity + { + return parent::modify_process($uid, $formDatas); + } public function modify($uid, object $dto): TrafficEntity { if (!$dto instanceof TrafficDTO) { diff --git a/app/Services/UserService.php b/app/Services/UserService.php index 4c5e520..dcc6f4f 100644 --- a/app/Services/UserService.php +++ b/app/Services/UserService.php @@ -18,6 +18,10 @@ class UserService extends CommonService parent::__construct($model); $this->addClassPaths('User'); } + public function createDTO(array $formDatas): UserDTO + { + return new UserDTO($formDatas); + } public function getFormService(): UserForm { if ($this->_form === null) { @@ -47,14 +51,17 @@ class UserService extends CommonService return $this->_helper; } //기본 기능부분 + protected function getEntity_process(mixed $entity): UserEntity + { + return $entity; + } protected function create_process(array $formDatas): UserEntity { //confirmpassword 필드는 Entity에 필요없으므로 제거 if (isset($formDatas['confirmpassword'])) { unset($formDatas['confirmpassword']); } - $entity = parent::create_process($formDatas); - return $entity; + return new UserEntity($formDatas); } public function create(object $dto): UserEntity { @@ -69,8 +76,7 @@ class UserService extends CommonService if (isset($formDatas['confirmpassword'])) { unset($formDatas['confirmpassword']); } - $entity = parent::modify_process($uid, $formDatas); - return $entity; + return parent::modify_process($uid, $formDatas); } public function modify($uid, object $dto): UserEntity {