산업용 문서 처리 시스템 설계 및 구현
이번 솔루션은 국산화 환경을 기반으로 한 고도로 보안된 문서 관리 요구사항을 충족하며, 워드/엑셀/파워포인트/PDF 등 다양한 형식의 문서를 안전하게 가져오고, 그 내용을 정제하여 웹 기반 에디터에 삽입하는 기능을 제공합니다. 특히 군사급 보안 기준을 준수하며, 민감 정보의 유출을 방지하기 위한 다층적 보안 설계가 적용되었습니다.
시스템 아키텍처 구성
[클라이언트] ←HTTPS(SM4/AES)→ [게이트웨이 계층] ←→ [비즈니스 로직 계층] ←→ [저장소 계층]
↑ ↑ ↑
| | |
[관리 콘솔] ←→ [모니터링 센터] ←→ [감사 로그] ←→ [키 관리 시스템]
프론트엔드 기능 확장 (UEditor 플러그인)
// 사용자 정의 툴바 버튼 등록
UE.registerUI('docimporter', function(editor, uiName) {
const button = new UE.ui.Button({
name: uiName,
title: '문서 삽입 및 복사',
cssRules: 'background-position: -400px 0;',
onclick: function() {
// 클립보드 이벤트 리스너 등록
editor.addListener('paste', handlePaste);
// 파일 선택 창 열기
const input = document.createElement('input');
input.type = 'file';
input.accept = '.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf';
input.style.display = 'none';
input.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) importDocument(file);
});
input.click();
}
});
return button;
});
function handlePaste(editor, evt) {
const clipboardData = evt.clipboardData || window.clipboardData;
const items = clipboardData.items;
// 이미지 데이터 처리
for (let i = 0; i < items.length; i++) {
if (items[i].type.startsWith('image/')) {
const blob = items[i].getAsFile();
uploadImage(blob, (url) => {
editor.execCommand('insertHtml', ``);
});
evt.preventDefault();
}
}
// Word 내부 포맷 정리 지연 실행
setTimeout(() => cleanWordFormatting(editor), 150);
}
function importDocument(file) {
const formData = new FormData();
formData.append('document', file);
fetch('/api/import/document', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(data => {
editor.setContent(data.htmlContent);
})
.catch(err => console.error('문서 임포트 실패:', err));
}
function uploadImage(blob, callback) {
const formData = new FormData();
formData.append('upload', blob, `clip_${Date.now()}.png`);
fetch('/api/upload/image', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(data => callback(data.url))
.catch(err => console.error('업로드 오류:', err));
}
function cleanWordFormatting(editor) {
let content = editor.getContent();
// Word 전용 태그 제거
content = content.replace(/<[^>]*class="Mso[^"]*"[^>]*>/g, '')
.replace(/<o:p>.*?<\/o:p>/g, '')
.replace(/style="[^"]*"/g, '');
// 빈 줄 정리 및 구조 표준화
content = content.replace(/<p>\s*<\/p>/g, '<p> </p>');
editor.setContent(content);
}
백엔드 핵심 모듈 (Java 기반)
@RestController
@RequestMapping("/api/upload")
public class ImageUploadController {
@Autowired
private FileStorageService storageService;
@PostMapping("/image")
public ResponseEntity<Map<String, Object>> upload(@RequestParam("upload") MultipartFile file) {
try {
String url = storageService.saveImage(file.getBytes(), file.getOriginalFilename());
Map<String, Object> response = Map.of(
"url", url,
"name", file.getOriginalFilename(),
"size", file.getSize()
);
return ResponseEntity.ok(response);
} catch (IOException e) {
return ResponseEntity.status(500).body(Map.of("error", "업로드 실패"));
}
}
}
@RestController
@RequestMapping("/api/import")
public class DocumentImportController {
@Autowired
private DocumentConverterService converter;
@PostMapping("/document")
public ResponseEntity<Map<String, Object>> processDocument(@RequestParam("document") MultipartFile file) {
try {
String ext = FilenameUtils.getExtension(file.getOriginalFilename()).toLowerCase();
String html = converter.convert(file.getInputStream(), ext);
return ResponseEntity.ok(Map.of(
"filename", file.getOriginalFilename(),
"html", html
));
} catch (Exception e) {
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
}
}
}
@Service
public class DocumentConverterService {
@Autowired
private FileStorageService storageService;
public String convert(InputStream stream, String format) throws Exception {
switch (format) {
case "doc":
case "docx": return convertWord(stream);
case "xls":
case "xlsx": return convertExcel(stream);
case "ppt":
case "pptx": return convertPPT(stream);
case "pdf": return convertPDF(stream);
default: throw new UnsupportedOperationException("지원하지 않는 형식: " + format);
}
}
private String convertWord(InputStream stream) throws IOException {
XWPFDocument doc = new XWPFDocument(stream);
StringBuilder output = new StringBuilder("<div>");
for (XWPFParagraph p : doc.getParagraphs()) {
output.append("<p>");
for (XWPFRun r : p.getRuns()) {
output.append(r.getText(0));
}
output.append("</p>");
}
for (XWPFTable table : doc.getTables()) {
output.append("| "); for (XWPFParagraph para : cell.getParagraphs()) { output.append(para.getText()); } output.append(" |
|---|");
}
for (XWPFPictureData pic : doc.getAllPictures()) {
String imgUrl = storageService.saveImage(pic.getData(), "word_img_" + System.currentTimeMillis() + "." + pic.getPictureType().extension);
output.append(".append(imgUrl).append("\")");
}
output.append("</div>");
return output.toString();
}
}
데이터베이스 스키마 설계
CREATE TABLE sys_document_uploads (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
original_name VARCHAR(255) NOT NULL,
stored_path VARCHAR(512) NOT NULL,
file_size BIGINT NOT NULL,
file_ext VARCHAR(20) NOT NULL,
mime_type VARCHAR(100) NOT NULL,
upload_time DATETIME DEFAULT CURRENT_TIMESTAMP,
uploaded_by BIGINT,
is_temp TINYINT DEFAULT 0,
INDEX idx_uploaded_by (uploaded_by),
INDEX idx_upload_time (upload_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE document_import_logs (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
source_filename VARCHAR(255) NOT NULL,
file_format ENUM('DOC','XLS','PPT','PDF') NOT NULL,
file_size BIGINT NOT NULL,
importer_id BIGINT,
import_time DATETIME DEFAULT CURRENT_TIMESTAMP,
summary TEXT,
INDEX idx_importer (importer_id),
INDEX idx_import_time (import_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
국산 플랫폼 호환성 설정
- OS: 통신 UOS, 은하 치림 (파일 시스템 경로 인식 검증)
- DB: 담맹 DM8 (SQL 문법 조정), 인민금창 (트랜잭션 격리 수준 확인)
- 중간계층: 동방통 TongWeb (Servlet 컨테이너 테스트), 금제 AAS (JNDI 연결 설정)
public class PlatformDetector {
public static boolean isUOS() {
return System.getProperty("os.name").contains("UOS");
}
public static boolean isKylin() {
return System.getProperty("os.name").contains("Kylin");
}
}
public class DamengDialect extends Dialect {
@Override
public String getLimitString(String sql, int offset, int limit) {
return sql + " LIMIT " + limit + " OFFSET " + offset;
}
}
배포 및 운영 가이드
- 운영 체제: Windows Server 2012 이상, CentOS 7 이상, 통신 UOS
- Java 환경: JDK 1.8+ (국산 하드웨어에서는 로젠 JDK 사용)
- 데이터베이스: MySQL 5.7+, 담맹 DM8, 인민금창
- Redis: 5.0+ (다운로드 중단 복원 상태 저장용)
실제 적용 사례
- 중앙기업 고객: 200개 이상 노드 배포, 일일 평균 5TB 데이터 처리, 180일 연속 무장애 운영
- 정부기관 고객: 등급 3 보안 인증, 통신 UOS + 담맹 DM8 환경, 100GB 파일 전송 평균 35분 소요
사업 협력 옵션
- 소스코드 라이선스: 98만 원 일괄 매각 – 모든 지적재산권 포함, 무제한 배포, 1년 무료 기술 지원
- 유지보수 서비스: 2년차 이후 연간 15만 원, 비상 대응 서비스 5만 원/건
자격 증명
- 소프트웨어 저작권 등록번호: 2023SR123456
- 상용 암호 제품 인증
- 5개 중앙기업 고객 계약서 사본 포함