Merge pull request '로그테이블 관련 내용 추가' (#317) from feature/screen-management into main
Reviewed-on: http://39.117.244.52:3000/kjs/ERP-node/pulls/317
This commit is contained in:
commit
e902987e44
|
|
@ -335,9 +335,222 @@ ON CONFLICT (table_name, column_name) DO UPDATE SET column_label = EXCLUDED.colu
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. 체크리스트
|
## 7. 로그 테이블 생성 (선택사항)
|
||||||
|
|
||||||
테이블 생성/수정 시 반드시 확인할 사항:
|
변경 이력 추적이 필요한 테이블에는 로그 테이블을 생성할 수 있습니다.
|
||||||
|
|
||||||
|
### 7.1 로그 테이블 DDL 템플릿
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 로그 테이블 생성
|
||||||
|
CREATE TABLE 테이블명_log (
|
||||||
|
log_id SERIAL PRIMARY KEY,
|
||||||
|
operation_type VARCHAR(10) NOT NULL, -- INSERT/UPDATE/DELETE
|
||||||
|
original_id VARCHAR(100), -- 원본 테이블 PK 값
|
||||||
|
changed_column VARCHAR(100), -- 변경된 컬럼명
|
||||||
|
old_value TEXT, -- 변경 전 값
|
||||||
|
new_value TEXT, -- 변경 후 값
|
||||||
|
changed_by VARCHAR(50), -- 변경자 ID
|
||||||
|
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 변경 시각
|
||||||
|
ip_address VARCHAR(50), -- 변경 요청 IP
|
||||||
|
user_agent TEXT, -- User Agent
|
||||||
|
full_row_before JSONB, -- 변경 전 전체 행
|
||||||
|
full_row_after JSONB -- 변경 후 전체 행
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 인덱스 생성
|
||||||
|
CREATE INDEX idx_테이블명_log_original_id ON 테이블명_log(original_id);
|
||||||
|
CREATE INDEX idx_테이블명_log_changed_at ON 테이블명_log(changed_at);
|
||||||
|
CREATE INDEX idx_테이블명_log_operation ON 테이블명_log(operation_type);
|
||||||
|
|
||||||
|
-- 코멘트 추가
|
||||||
|
COMMENT ON TABLE 테이블명_log IS '테이블명 테이블 변경 이력';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 트리거 함수 DDL 템플릿
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE OR REPLACE FUNCTION 테이블명_log_trigger_func()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
DECLARE
|
||||||
|
v_column_name TEXT;
|
||||||
|
v_old_value TEXT;
|
||||||
|
v_new_value TEXT;
|
||||||
|
v_user_id VARCHAR(50);
|
||||||
|
v_ip_address VARCHAR(50);
|
||||||
|
BEGIN
|
||||||
|
v_user_id := current_setting('app.user_id', TRUE);
|
||||||
|
v_ip_address := current_setting('app.ip_address', TRUE);
|
||||||
|
|
||||||
|
IF (TG_OP = 'INSERT') THEN
|
||||||
|
INSERT INTO 테이블명_log (operation_type, original_id, changed_by, ip_address, full_row_after)
|
||||||
|
VALUES ('INSERT', NEW.id, v_user_id, v_ip_address, row_to_json(NEW)::jsonb);
|
||||||
|
RETURN NEW;
|
||||||
|
|
||||||
|
ELSIF (TG_OP = 'UPDATE') THEN
|
||||||
|
FOR v_column_name IN
|
||||||
|
SELECT column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = '테이블명'
|
||||||
|
AND table_schema = 'public'
|
||||||
|
LOOP
|
||||||
|
EXECUTE format('SELECT ($1).%I::TEXT, ($2).%I::TEXT', v_column_name, v_column_name)
|
||||||
|
INTO v_old_value, v_new_value
|
||||||
|
USING OLD, NEW;
|
||||||
|
|
||||||
|
IF v_old_value IS DISTINCT FROM v_new_value THEN
|
||||||
|
INSERT INTO 테이블명_log (
|
||||||
|
operation_type, original_id, changed_column, old_value, new_value,
|
||||||
|
changed_by, ip_address, full_row_before, full_row_after
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
'UPDATE', NEW.id, v_column_name, v_old_value, v_new_value,
|
||||||
|
v_user_id, v_ip_address, row_to_json(OLD)::jsonb, row_to_json(NEW)::jsonb
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END LOOP;
|
||||||
|
RETURN NEW;
|
||||||
|
|
||||||
|
ELSIF (TG_OP = 'DELETE') THEN
|
||||||
|
INSERT INTO 테이블명_log (operation_type, original_id, changed_by, ip_address, full_row_before)
|
||||||
|
VALUES ('DELETE', OLD.id, v_user_id, v_ip_address, row_to_json(OLD)::jsonb);
|
||||||
|
RETURN OLD;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NULL;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 트리거 DDL 템플릿
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TRIGGER 테이블명_audit_trigger
|
||||||
|
AFTER INSERT OR UPDATE OR DELETE ON 테이블명
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION 테이블명_log_trigger_func();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4 로그 설정 등록
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO table_log_config (
|
||||||
|
original_table_name, log_table_name, trigger_name,
|
||||||
|
trigger_function_name, is_active, created_by, created_at
|
||||||
|
) VALUES (
|
||||||
|
'테이블명', '테이블명_log', '테이블명_audit_trigger',
|
||||||
|
'테이블명_log_trigger_func', 'Y', '생성자ID', now()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.5 table_labels에 use_log_table 플래그 설정
|
||||||
|
|
||||||
|
```sql
|
||||||
|
UPDATE table_labels
|
||||||
|
SET use_log_table = 'Y', updated_date = now()
|
||||||
|
WHERE table_name = '테이블명';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.6 전체 예시: order_info 로그 테이블 생성
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Step 1: 로그 테이블 생성
|
||||||
|
CREATE TABLE order_info_log (
|
||||||
|
log_id SERIAL PRIMARY KEY,
|
||||||
|
operation_type VARCHAR(10) NOT NULL,
|
||||||
|
original_id VARCHAR(100),
|
||||||
|
changed_column VARCHAR(100),
|
||||||
|
old_value TEXT,
|
||||||
|
new_value TEXT,
|
||||||
|
changed_by VARCHAR(50),
|
||||||
|
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
ip_address VARCHAR(50),
|
||||||
|
user_agent TEXT,
|
||||||
|
full_row_before JSONB,
|
||||||
|
full_row_after JSONB
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_order_info_log_original_id ON order_info_log(original_id);
|
||||||
|
CREATE INDEX idx_order_info_log_changed_at ON order_info_log(changed_at);
|
||||||
|
CREATE INDEX idx_order_info_log_operation ON order_info_log(operation_type);
|
||||||
|
|
||||||
|
COMMENT ON TABLE order_info_log IS 'order_info 테이블 변경 이력';
|
||||||
|
|
||||||
|
-- Step 2: 트리거 함수 생성
|
||||||
|
CREATE OR REPLACE FUNCTION order_info_log_trigger_func()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
DECLARE
|
||||||
|
v_column_name TEXT;
|
||||||
|
v_old_value TEXT;
|
||||||
|
v_new_value TEXT;
|
||||||
|
v_user_id VARCHAR(50);
|
||||||
|
v_ip_address VARCHAR(50);
|
||||||
|
BEGIN
|
||||||
|
v_user_id := current_setting('app.user_id', TRUE);
|
||||||
|
v_ip_address := current_setting('app.ip_address', TRUE);
|
||||||
|
|
||||||
|
IF (TG_OP = 'INSERT') THEN
|
||||||
|
INSERT INTO order_info_log (operation_type, original_id, changed_by, ip_address, full_row_after)
|
||||||
|
VALUES ('INSERT', NEW.id, v_user_id, v_ip_address, row_to_json(NEW)::jsonb);
|
||||||
|
RETURN NEW;
|
||||||
|
ELSIF (TG_OP = 'UPDATE') THEN
|
||||||
|
FOR v_column_name IN
|
||||||
|
SELECT column_name FROM information_schema.columns
|
||||||
|
WHERE table_name = 'order_info' AND table_schema = 'public'
|
||||||
|
LOOP
|
||||||
|
EXECUTE format('SELECT ($1).%I::TEXT, ($2).%I::TEXT', v_column_name, v_column_name)
|
||||||
|
INTO v_old_value, v_new_value USING OLD, NEW;
|
||||||
|
IF v_old_value IS DISTINCT FROM v_new_value THEN
|
||||||
|
INSERT INTO order_info_log (operation_type, original_id, changed_column, old_value, new_value, changed_by, ip_address, full_row_before, full_row_after)
|
||||||
|
VALUES ('UPDATE', NEW.id, v_column_name, v_old_value, v_new_value, v_user_id, v_ip_address, row_to_json(OLD)::jsonb, row_to_json(NEW)::jsonb);
|
||||||
|
END IF;
|
||||||
|
END LOOP;
|
||||||
|
RETURN NEW;
|
||||||
|
ELSIF (TG_OP = 'DELETE') THEN
|
||||||
|
INSERT INTO order_info_log (operation_type, original_id, changed_by, ip_address, full_row_before)
|
||||||
|
VALUES ('DELETE', OLD.id, v_user_id, v_ip_address, row_to_json(OLD)::jsonb);
|
||||||
|
RETURN OLD;
|
||||||
|
END IF;
|
||||||
|
RETURN NULL;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- Step 3: 트리거 생성
|
||||||
|
CREATE TRIGGER order_info_audit_trigger
|
||||||
|
AFTER INSERT OR UPDATE OR DELETE ON order_info
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION order_info_log_trigger_func();
|
||||||
|
|
||||||
|
-- Step 4: 로그 설정 등록
|
||||||
|
INSERT INTO table_log_config (original_table_name, log_table_name, trigger_name, trigger_function_name, is_active, created_by, created_at)
|
||||||
|
VALUES ('order_info', 'order_info_log', 'order_info_audit_trigger', 'order_info_log_trigger_func', 'Y', 'system', now());
|
||||||
|
|
||||||
|
-- Step 5: table_labels 플래그 업데이트
|
||||||
|
UPDATE table_labels SET use_log_table = 'Y', updated_date = now() WHERE table_name = 'order_info';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.7 로그 테이블 삭제
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 트리거 삭제
|
||||||
|
DROP TRIGGER IF EXISTS 테이블명_audit_trigger ON 테이블명;
|
||||||
|
|
||||||
|
-- 트리거 함수 삭제
|
||||||
|
DROP FUNCTION IF EXISTS 테이블명_log_trigger_func();
|
||||||
|
|
||||||
|
-- 로그 테이블 삭제
|
||||||
|
DROP TABLE IF EXISTS 테이블명_log;
|
||||||
|
|
||||||
|
-- 로그 설정 삭제
|
||||||
|
DELETE FROM table_log_config WHERE original_table_name = '테이블명';
|
||||||
|
|
||||||
|
-- table_labels 플래그 업데이트
|
||||||
|
UPDATE table_labels SET use_log_table = 'N', updated_date = now() WHERE table_name = '테이블명';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 체크리스트
|
||||||
|
|
||||||
|
### 테이블 생성/수정 시 반드시 확인할 사항:
|
||||||
|
|
||||||
- [ ] DDL에 기본 5개 컬럼 포함 (id, created_date, updated_date, writer, company_code)
|
- [ ] DDL에 기본 5개 컬럼 포함 (id, created_date, updated_date, writer, company_code)
|
||||||
- [ ] 모든 비즈니스 컬럼은 `VARCHAR(500)` 타입 사용
|
- [ ] 모든 비즈니스 컬럼은 `VARCHAR(500)` 타입 사용
|
||||||
|
|
@ -349,9 +562,18 @@ ON CONFLICT (table_name, column_name) DO UPDATE SET column_label = EXCLUDED.colu
|
||||||
- [ ] code/entity 타입은 detail_settings에 참조 정보 포함
|
- [ ] code/entity 타입은 detail_settings에 참조 정보 포함
|
||||||
- [ ] ON CONFLICT 절로 중복 시 UPDATE 처리
|
- [ ] ON CONFLICT 절로 중복 시 UPDATE 처리
|
||||||
|
|
||||||
|
### 로그 테이블 생성 시 확인할 사항 (선택):
|
||||||
|
|
||||||
|
- [ ] 로그 테이블 생성 (`테이블명_log`)
|
||||||
|
- [ ] 인덱스 3개 생성 (original_id, changed_at, operation_type)
|
||||||
|
- [ ] 트리거 함수 생성 (`테이블명_log_trigger_func`)
|
||||||
|
- [ ] 트리거 생성 (`테이블명_audit_trigger`)
|
||||||
|
- [ ] `table_log_config`에 로그 설정 등록
|
||||||
|
- [ ] `table_labels.use_log_table = 'Y'` 업데이트
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. 금지 사항
|
## 9. 금지 사항
|
||||||
|
|
||||||
1. **DB 타입 직접 지정 금지**: NUMBER, INTEGER, DATE 등 DB 타입 직접 사용 금지
|
1. **DB 타입 직접 지정 금지**: NUMBER, INTEGER, DATE 등 DB 타입 직접 사용 금지
|
||||||
2. **VARCHAR 길이 변경 금지**: 반드시 `VARCHAR(500)` 사용
|
2. **VARCHAR 길이 변경 금지**: 반드시 `VARCHAR(500)` 사용
|
||||||
|
|
@ -364,5 +586,7 @@ ON CONFLICT (table_name, column_name) DO UPDATE SET column_label = EXCLUDED.colu
|
||||||
## 참조 파일
|
## 참조 파일
|
||||||
|
|
||||||
- `backend-node/src/services/ddlExecutionService.ts`: DDL 실행 서비스
|
- `backend-node/src/services/ddlExecutionService.ts`: DDL 실행 서비스
|
||||||
|
- `backend-node/src/services/tableManagementService.ts`: 로그 테이블 생성 서비스
|
||||||
- `backend-node/src/types/ddl.ts`: DDL 타입 정의
|
- `backend-node/src/types/ddl.ts`: DDL 타입 정의
|
||||||
- `backend-node/src/controllers/ddlController.ts`: DDL API 컨트롤러
|
- `backend-node/src/controllers/ddlController.ts`: DDL API 컨트롤러
|
||||||
|
- `backend-node/src/controllers/tableManagementController.ts`: 로그 테이블 API 컨트롤러
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue