trafficmonitor init...2

This commit is contained in:
choi.jh 2025-11-06 19:43:50 +09:00
parent 65dff253d8
commit c79db51fac
12 changed files with 238 additions and 256 deletions

View File

@ -384,3 +384,16 @@ define("STATUS", [
'PAID' => 'paid',
'UNPAID' => 'unpaid',
]);
//ROLE
define("ROLE", [
'USER' => [
'MANAGER' => "manager",
'CLOUDFLARE' => "cloudflare",
'FIREWALL' => "firewall",
'SECURITY' => "security",
'DIRECTOR' => "director",
'MASTER' => "master",
],
['CLIENT'] => []
]);

View File

@ -37,10 +37,23 @@ class UserController extends AdminController
return parent::getFormRule($action, $field, $rule);
}
//Index,FieldForm관련.
protected function create_process(): UserEntity
protected function create_process(): RedirectResponse
{
//요청 데이터를 DTO 객체로 변환
$dto = new UserDTO($this->request->getPost());
return $this->service->create($dto);
$entity = $this->service->create($dto);
$redirect_url = $this->authService->popPreviousUrl() ?? implode(DIRECTORY_SEPARATOR, $this->getActionPaths());
return redirect()->to($redirect_url)->with('success', "{$entity->getTitle()} 계정 생성이 완료되었습니다.");
}
protected function modify_form_process($uid): UserEntity
{
if (!$uid) {
throw new \Exception("계정 번호가 정의 되지 않았습니다.");
}
$entity = $this->service->getEntity($uid);
if (!$entity instanceof UserEntity) {
throw new \Exception("{$uid}에 해당하는 계정을 찾을수 없습니다.");
}
return $entity;
}
}

View File

@ -117,7 +117,7 @@ abstract class CommonController extends BaseController
return view($full_path, ['viewDatas' => $view_datas]);
}
protected function create_form_process(): void {}
final public function create_form(): string|RedirectResponse
final public function create_form(): string
{
$action = __FUNCTION__;
try {
@ -138,19 +138,14 @@ abstract class CommonController extends BaseController
$this->getViewDatas()
);
}
protected function create_process(): mixed
{
return $this->service->create();
}
abstract protected function create_process(): RedirectResponse;
final public function create(): RedirectResponse
{
$action = __FUNCTION__;
try {
//초기화
$this->action_init_process($action);
$this->create_process();
$redirect_url = $this->authService->popPreviousUrl() ?? implode(DIRECTORY_SEPARATOR, $this->getActionPaths());
return redirect()->to($redirect_url)->with('success', static::class . "/{$action}이 완료되었습니다.");
return $this->create_process();
} catch (ValidationException $e) {
// 검증 실패 시 폼으로 돌아가서 오류 메시지 표시
log_message('error', $e->getMessage());
@ -160,7 +155,29 @@ abstract class CommonController extends BaseController
return redirect()->back()->withInput()->with('error', $e->getMessage());
}
}
abstract protected function modify_form_process($uid): mixed;
final public function modify_form($uid): string
{
$action = __FUNCTION__;
try {
//초기화
$this->action_init_process($action);
$entity = $this->modify_form_process($uid);
$this->addViewDatas('entity', $entity);
$this->action_render_process($action);
// dd($this->getViewDatas());
} catch (\Exception $e) {
log_message('error', $e->getMessage());
$this->addViewDatas(self::ACTION_RESULT, 'error');
$this->addViewDatas(self::ACTION_MESSAGE, $e->getMessage());
//오류발생시 리디렉션 대신 폼 뷰를 다시 렌더링하도록 action_view_process 호출
}
return $this->action_result_process(
$this->getActionPaths(),
$action,
$this->getViewDatas()
);
}
//리스트관련
//조건절 처리
// protected function index_condition_process(): void

View File

@ -7,10 +7,11 @@ class UserDTO extends CommonDTO
public ?int $uid = null;
public ?string $id = null;
public ?string $passwd = null;
public ?string $passwd_confirm = null;
public ?string $confirmpassword = null;
public ?string $name = null;
public ?string $email = null;
public ?string $mobile = null;
public ?string $role = null;
public ?string $role = null;
public ?string $status = null;
public function __construct(array $datas = [])
@ -18,7 +19,12 @@ class UserDTO extends CommonDTO
parent::__construct();
foreach ($datas as $key => $value) {
if (property_exists($this, $key)) {
$this->{$key} = $value;
if ($key === 'role' && is_array($value)) {
// 배열일 경우, 쉼표로 구분된 문자열로 변환하여 저장
$this->role = implode(DEFAULTS["DELIMITER_ROLE"], $value);
} else {
$this->{$key} = $value;
}
}
}
}
@ -28,7 +34,8 @@ class UserDTO extends CommonDTO
'uid' => $this->uid,
'id' => $this->id,
'passwd' => $this->passwd,
'passwd_confirm' => $this->passwd_confirm,
'confirmpassword' => $this->confirmpassword,
'name' => $this->name,
'email' => $this->email,
'mobile' => $this->mobile,
'role' => $this->role,

View File

@ -9,18 +9,38 @@ class UserEntity extends CommonEntity
{
const PK = Model::PK;
const TITLE = Model::TITLE;
const DEFAULT_STATUS = STATUS['AVAILABLE'];
const DEFAULT_STATUS = STATUS['AVAILABLE'];
// 💡 1. $casts 속성 추가:
// DB에 JSON 형태로 저장된 'role' 컬럼을 PHP에서 배열로 자동 변환합니다.
protected $casts = [
'role' => 'json-array',
];
public function getID(): string
{
return $this->attributes['id'];
return (string) $this->attributes['id'];
}
public function getPassword(): string
{
return $this->attributes['passwd'];
}
public function getRole(): string
// $formDatas['passwd']에 평문 비밀번호가 들어있으면,
//Model->insert시 Entity 생성자($formDatas)가 setPasswd()를 자동으로 호출합니다.
public function setPasswd(string $password)
{
return $this->attributes['role'];
$this->attributes['passwd'] = password_hash($password, PASSWORD_BCRYPT);
}
/**
* 사용자의 역할을 배열 형태로 반환합니다.
* $casts에 의해 DB에서 읽어올 이미 배열로 변환되어 있습니다.
* @return array
*/
public function getRole(): array
{
// 💡 2. 반환 타입을 array로 변경하고,
// null일 경우를 대비해 빈 배열을 반환하도록 처리합니다.
return $this->attributes['role'] ?? [];
}
}

View File

@ -15,6 +15,29 @@ class UserHelper extends CommonHelper
case 'confirmpassword':
$form = form_password($field, "", [...$extras]);
break;
case 'role':
// 💡 $value는 수정 폼 로드시 현재 Entity의 'role' 배열입니다.
// (old() 값이 있다면 old() 배열이 됩니다.)
$currentRoles = is_array($value) ? $value : [];
// 사용 가능한 모든 역할 목록 (Service 등에서 가져온다고 가정)
$allRoles = $viewDatas['control']['formOptions']['role'] ?? ROLE['USER'];
$form = '';
// 2. 각 역할에 대한 체크박스를 순회하며 생성
foreach ($allRoles as $roleValue => $roleLabel) {
// $roleLabel이 배열이 아닌 문자열만 있는 경우를 대비
if (is_int($roleValue)) {
$roleValue = $roleLabel;
$roleLabel = ucfirst($roleValue);
}
$checked = in_array($roleValue, $currentRoles);
// 3. name="role[]" 형태로 배열 데이터 전송 준비
$form .= '<label class="me-3">';
$form .= form_checkbox('role[]', $roleValue, $checked, array_merge(['id' => "role_{$roleValue}"], $extras));
$form .= " {$roleLabel}";
$form .= '</label>';
}
break;
default:
$form = parent::getFieldForm($field, $value, $viewDatas, $extras);
break;

View File

@ -16,12 +16,12 @@ return [
'deleted_at' => "삭제일",
],
"ROLE" => [
"manager" => "관리자",
"cloudflare" => "Cloudflare관리자",
"firewall" => "firewall관리자",
"security" => "보안관리자",
"director" => "감독자",
"master" => "마스터",
ROLE['USER']["MANAGER"] => "관리자",
ROLE['USER']["CLOUDFLARE"] => "Cloudflare관리자",
ROLE['USER']["FIREWALL"] => "firewall관리자",
ROLE['USER']["SECURITY"] => "보안관리자",
ROLE['USER']["DIRECTOR"] => "감독자",
ROLE['USER']["MASTER"] => "마스터",
],
"STATUS" => [
STATUS['AVAILABLE'] => "사용중",

View File

@ -64,149 +64,104 @@ abstract class CommonModel extends Model
{
return $this->useAutoIncrement;
}
//Primary Key로 uuid를 사용시 해당 모델에 아래 변수 반드시 추가 필요
// protected $useAutoIncrement = false;
// protected $beforeInsert = ['generateUUID'];
// allowedFields에는 PK넣으면 않됨, Column Type: CHAR(36)
//
final protected function generateUUID(): string
/**
* Entity 삽입을 처리하고, $useAutoIncrement가 false일 경우 UUID를 자동으로 생성합니다.
* @param object|array $data Entity 객체 또는 데이터 배열
* @param bool $returnID 삽입 ID를 반환할지 여부 (자동 증가 키일 경우 true)
* @return bool|int|string
*/
public function insert($data = null, bool $returnID = true)
{
$data = random_bytes(16);
// UUID version 4 (random)
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); // version 0100
$data[8] = chr(ord($data[8]) & 0x3f | 0x80); // variant 10
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
public function getFormRule(string $action, string $field): string
{
if (is_array($field)) {
throw new \Exception(__FUNCTION__ . "=> field가 array 입니다.\n" . var_export($field, true));
// 1. 공통 전처리 로직 (예: 모든 모델에 대한 로그 기록, 공통 필드 설정 등)
$data = $this->preProcessData($data);
// 2. PK 자동 할당 로직
if (!$this->useAutoIncrement) {
$data = $this->ensurePrimaryKey($data);
}
switch ($field) {
case $this->getPKField():
// 수동입력인 경우
if (!$this->useAutoIncrement) {
$rule = "required|regex_match[/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/]";
$rule .= in_array($action, ["create", "create_form"]) ? "|is_unique[" . $this->getTable() . "." . $field . "]" : "";
} else {
$rule = "required|numeric";
}
break;
case $this->getTitleField():
$rule = "required|trim|string";
break;
case "code":
// a-zA-Z → 영문 대소문자,0-9 → 숫자,가-힣 → 한글 완성형,\- → 하이픈
$rule = "required|regex_match[/^[a-zA-Z0-9가-힣\-]+$/]|min_length[4]|max_length[20]";
$rule .= in_array($action, ["create", "create_form"]) ? "|is_unique[" . $this->getTable() . "." . $field . "]" : "";
break;
case 'picture':
$rule = "is_image[{$field}]|mime_in[{$field},image/jpg,image/jpeg,image/gif,image/png,image/webp]|max_size[{$field},300]|max_dims[{$field},2048,768]";
break;
case "updated_at":
case "created_at":
case "deleted_at":
$rule = "permit_empty|valid_date";
break;
default:
$rule = "permit_empty|trim|string";
break;
}
return $rule;
}
protected function convertFormDatas(string $action, string $field, array $formDatas): mixed
{
// 필드 값 존재 여부 확인
$value = array_key_exists($field, $formDatas) ? $formDatas[$field] : null;
switch ($field) {
case $this->getPKField():
// 수동입력인 경우
if (!$this->useAutoIncrement) {
$randomBytes = bin2hex(random_bytes(32));
$value = sprintf(
'%08s-%04s-%04x-%04x-%12s',
substr($randomBytes, 0, 8),
substr($randomBytes, 8, 4),
substr($randomBytes, 12, 4),
substr($randomBytes, 16, 4),
substr($randomBytes, 20, 12)
);
}
break;
case "editor":
case "detail":
case "content":
case "discription":
case "history":
if ($value !== '' && $value !== null) {
$value = htmlentities($value, ENT_QUOTES, 'UTF-8');
}
break;
}
return $value;
}
//기본 기능
public function create(array $formDatas): mixed
{
$convertedFormDatas = [];
foreach ($this->allowedFields as $field) {
$value = $this->convertFormDatas(
__FUNCTION__,
$field,
$formDatas
);
if ($value !== '' && $value !== null) {
$convertedFormDatas[$field] = $value;
}
}
// 최종 저장 시 오류 발생하면
// dd($convertedFormDatas);
if (!$this->save($convertedFormDatas)) {
$message = sprintf(
"\n------%s 오류-----\n%s\n%s\n------------------------------\n",
__METHOD__,
var_export($this->errors(), true),
$this->getLastQuery()
);
log_message('error', $message);
throw new \Exception($message);
}
//Model별 returntype형의 Entity 호출
if (!class_exists($this->returnType)) {
throw new \RuntimeException(__METHOD__ . "에서 returnType: {$this->returnType}이 정의되지 않았습니다.");
}
$entity = new $this->returnType($convertedFormDatas);
// primaryKey가 자동입력이면
// 3. BaseModel의 insert 호출
// 문자열 PK일 경우, $returnID를 false로 넘겨서 ID 반환을 강제하지 않는 것이 좋습니다.
$baseReturn = parent::insert($data, $this->useAutoIncrement);
// 4. 후처리 및 반환
if ($this->useAutoIncrement) {
$pkField = $this->getPKField();
$entity->$pkField = $this->getInsertID();
// 정수 PK: BaseModel이 반환한 새로운 ID를 그대로 반환
return $baseReturn;
} else {
// 문자열 PK: BaseModel의 insert는 성공 시 true/1을 반환. true로 통일하여 반환
return $baseReturn !== false;
}
return $entity;
}
public function modify(mixed $entity, array $formDatas): mixed
/**
* Entity나 데이터 배열을 받아서 필요하다면 Primary Key를 생성하여 할당합니다.
* @param object|array $data
* @return object|array
*/
protected function ensurePrimaryKey($data)
{
//수정일추가
$formDatas['updated_at'] = date("Y-m-d H:i:s");
foreach (array_keys($formDatas) as $field) {
$value = $this->convertFormDatas(
__FUNCTION__,
$field,
$formDatas
);
if ($entity->$field !== $value) {
$entity->$field = $value;
$pkName = $this->primaryKey;
$entityData = is_object($data) ? $data->attributes : (array) $data;
// PK가 설정되어 있지 않은 경우
if (empty($entityData[$pkName])) {
$newUuid = service('uuid')->uuid4()->toString();
if (is_object($data)) {
// Entity 객체일 경우 __set()을 통해 Setter를 호출하며 할당
$data->$pkName = $newUuid;
} else {
// 배열일 경우 배열에 할당
$data[$pkName] = $newUuid;
}
}
// 최종 저장 시 오류 발생하면
if (!$this->save($entity)) {
$message = sprintf(
"\n------%s 오류-----\n%s\n------------------------------\n",
__METHOD__,
var_export($this->errors(), true)
);
log_message('error', $message);
throw new \Exception($message);
}
return $entity;
return $data;
}
/**
* 모든 삽입/수정 작업 공통 로직을 처리하는 확장 지점.
*/
protected function preProcessData($data)
{
// 예: $data['data']['updated_by'] = session('user_id');
return $data;
}
// ******************************************************
// 2. UPDATE 및 DELETE 확장
// ******************************************************
/**
* BaseModel의 update 메서드를 호출하기 /후에 공통 로직을 적용하는 확장 지점.
* @param array|int|string|null $id
* @param array|object|null $data
* @return bool
*/
public function update($id = null, $data = null): bool
{
// 1. 업데이트 전 공통 로직 (preUpdate 이벤트)
$data = $this->preProcessData($data);
// 2. BaseModel의 update 호출
return parent::update($id, $data);
// 3. 업데이트 후 공통 로직 (postUpdate 이벤트)
}
/**
* BaseModel의 delete 메서드를 호출하기 /후에 공통 로직을 적용하는 확장 지점.
* @param array|int|string|null $id
* @param bool $purge
* @return bool
*/
public function delete($id = null, bool $purge = false): bool
{
// 1. 삭제 전 공통 로직 (preDelete 이벤트)
// 2. BaseModel의 delete 호출
return parent::delete($id, $purge);
// 3. 삭제 후 공통 로직 (postDelete 이벤트)
}
}

View File

@ -40,45 +40,6 @@ class TrafficModel extends CommonModel
parent::__construct();
}
public function getFormRule(string $action, string $field): string
{
if (is_array($field)) {
throw new \Exception(__FUNCTION__ . "=> field가 array 입니다.\n" . var_export($field, true));
}
switch ($field) {
case "user_uid":
case "clientinfo_uid":
case "serverinfo_uid":
case "amount":
case "rack":
case "line":
$rule = "required|numeric";
break;
case "sale":
case "payment_uid":
$rule = "permit_empty|numeric";
break;
case "site":
case "location":
case "status":
$rule = "required|trim|string";
break;
case "billing_at":
case "start_at":
$rule = "required|valid_date";
break;
case "end_at":
$rule = "permit_empty|valid_date";
break;
case 'title':
case "history":
$rule = "permit_empty|trim|string";
break;
default:
$rule = parent::getFormRule($action, $field);
break;
}
return $rule;
}
//입력전 코드처리
final public function create(array $formDatas): ServiceEntity
{

View File

@ -27,50 +27,4 @@ class UserModel extends CommonModel
{
parent::__construct();
}
public function getFormRule(string $action, string $field): string
{
if (is_array($field)) {
throw new \Exception(__FUNCTION__ . "=> field가 array 입니다.\n" . var_export($field, true));
}
switch ($field) {
case "id":
$rule = "required|trim|min_length[4]|max_length[20]";
$rule .= in_array($action, ["create", "create_form"]) ? "|is_unique[{$this->table}.{$field}]" : "";
break;
case "passwd":
$rule = in_array($action, ["create", "create_form"]) ? "required|trim|string" : "permit_empty|trim|string";
break;
case "confirmpassword":
$rule = in_array($action, ["create", "create_form"]) ? "required|trim|string|matches[passwd]" : "permit_empty|trim|string|matches[passwd]";
break;
case "email":
$rule = "required|trim|valid_email";
$rule .= in_array($action, ["create", "create_form"]) ? "|is_unique[{$this->table}.{$field}]" : "";
break;
case "role":
$rule = "required|trim|string";
break;
default:
$rule = parent::getFormRule($action, $field);
break;
}
return $rule;
}
protected function convertFormDatas(string $action, string $field, array $formDatas): mixed
{
// 필드 값 존재 여부 확인
$value = array_key_exists($field, $formDatas) ? $formDatas[$field] : null;
switch ($field) {
case "passwd":
$value = password_hash($value, PASSWORD_DEFAULT);
break;
case "confirmpassword":
$value = password_hash($value, PASSWORD_DEFAULT);
break;
default:
$value = parent::convertFormDatas($action, $field, $formDatas);
break;
}
return $value;
}
}

View File

@ -50,13 +50,31 @@ class UserService extends CommonService
{
// DTO 객체를 배열로 변환하여 검증기에 전달
$formDatas = (array) $dto;
// dd($formDatas);
if (!service('validation')->setRules($this->getFormRules(__FUNCTION__))->run($formDatas)) {
// 검증 실패 시, ValidationException을 던집니다.
throw new ValidationException(implode("\n", service('validation')->getErrors()));
}
$formDatas['role'] = implode(DEFAULTS["DELIMITER_ROLE"], $formDatas['role']);
$entity = $this->model->create($formDatas);
return $entity;
$entity = new UserEntity($formDatas);
$result = $this->model->insert($entity);
if (!$result) {
throw new \Exception("{$entity->getTitle()} 등록 중 DB 오류가 발생하였습니다.");
}
// 💡 PK 타입에 따른 최종 Entity 반환 로직 분기
if ($this->model->useAutoIncrement) {
// 1. 정수 PK인 경우: $result는 ID 번호이므로, 저장된 레코드를 DB에서 다시 조회합니다.
// (자동 설정된 필드(created_at 등)를 포함하기 위해 재조회)
$savedEntity = $this->model->find($result);
} else {
// 2. 문자열 PK인 경우: $result는 true/1 이므로,
// 이미 ID가 설정된 $entity 객체를 반환하거나, ID로 재조회합니다.
// Entity에 모든 필드(created_at 등)가 설정되지 않았다면 재조회가 안전합니다.
$pkValue = $entity->{$this->model->primaryKey};
$savedEntity = $this->model->find($pkValue);
}
if (!$savedEntity) {
throw new \Exception("등록된 데이터를 찾을 수 없습니다.");
}
return $savedEntity;
}
// protected function modify_process(mixed $entity, array $formDatas): UserEntity
// {

View File

@ -1,22 +1,23 @@
<?= $this->extend(LAYOUTS[$viewDatas['control']['layout']]['path']) ?>
<?= $this->section('content') ?>
<?php if ($error = session('error')): echo $viewDatas['service']->getHelper()->alertTrait($error) ?><?php endif ?>
<?php if (session('error')): echo $viewDatas['control']['helper']->alertTrait(session('error')) ?><?php endif ?>
<div id="container" class="content">
<div class="form_top"><?= $this->include("templates/{$viewDatas['control']['layout']}/form_content_top"); ?></div>
<?= form_open(current_url(), ['id' => 'action_form', ...$viewDatas['forms']['attributes']], $viewDatas['forms']['hiddens']) ?>
<?= form_open(current_url(), $viewDatas['forms']['attributes'], $viewDatas['forms']['hiddens']) ?>
<div class="action_form">
<table class="table table-bordered">
<?php foreach ($viewDatas['control']['formFields'] as $field): ?>
<tr>
<th nowrap class="text-end"><?= $viewDatas['service']->getHelper()->getFieldLabel($field, lang("{$viewDatas['class_path']}.label.{$field}"), $viewDatas) ?></th>
<th nowrap class="text-end"><?= $viewDatas['control']['helper']->getFieldLabel($field, "", $viewDatas) ?></th>
<?= dd($viewDatas); ?>
<td nowrap class="text-start">
<?= $viewDatas['service']->getHelper()->getFieldForm($field, old($field) ?? $viewDatas['entity']->$field ?? null, $viewDatas) ?>
<div><?= validation_show_error($field); ?></div>
<?= $viewDatas['control']['helper']->getFieldForm($field, old($field) ?? ($viewDatas['control']['entity']->$field ?? null), $viewDatas) ?>
<span><?= validation_show_error($field); ?></span>
</td>
</tr>
<?php endforeach; ?>
</table>
<div class="text-center"><?= form_submit("", '수정', ["class" => "btn btn-outline btn-primary"]) ?></div>
<div class="text-center"><?= form_submit('', '입력', array("class" => "btn btn-outline btn-primary")); ?></div>
<?= form_close(); ?>
</div>
<div class="form_bottom"><?= $this->include("templates/{$viewDatas['control']['layout']}/form_content_bottom"); ?></div>