Skip to content

iflex 1.8.0과 Firo 통합 가이드

개요

이 가이드는 iflex 1.8.0 프로젝트에 Firo 파일 관리 라이브러리를 안전하게 통합하는 방법을 설명합니다.

통합 전 체크리스트

1. 버전 호환성 확인

  • iflex 버전: 1.8.0+
  • Firo 버전: 1.3.1-SNAPSHOT+
  • Spring Boot: 3.4.4+
  • Java: 17+

2. 의존성 호환성 확인

  • MyBatis: iflex와 Firo 모두 지원
  • Spring Data JPA: 선택사항 (Firo에서만 사용 가능)

단계별 통합 가이드

1단계: 의존성 추가

Gradle 설정 (build.gradle)

gradle
dependencies {
    // 기존 iflex 의존성들...
    
    // Firo 의존성 추가 (iflex 의존성 이후에 추가)
    implementation 'com.unvus.firo:firo-core:1.3.1-SNAPSHOT'
    implementation 'com.unvus.firo:firo-mybatis:1.3.1-SNAPSHOT'  // iflex는 MyBatis 사용
}

Maven 설정 (pom.xml)

xml
<dependencies>
    <!-- 기존 iflex 의존성들... -->
    
    <!-- Firo 의존성 추가 -->
    <dependency>
        <groupId>com.unvus.firo</groupId>
        <artifactId>firo-core</artifactId>
        <version>1.3.1-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>com.unvus.firo</groupId>
        <artifactId>firo-mybatis</artifactId>
        <version>1.3.1-SNAPSHOT</version>
    </dependency>
</dependencies>

2단계: 설정 파일 구성

application.properties

properties
# === Firo 설정 ===
# Firo 활성화
firo.enabled=true

# MyBatis 사용 (iflex와 동일)
firo.db.type=mybatis

# 파일 업로드 기본 설정
firo.directory.base-dir=/var/uploads/
firo.directory.tmp-dir=/var/uploads/temp/
firo.cdn-url=https://cdn.yourdomain.com/

# 최대 파일 크기 설정
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB

# === iflex 기존 설정은 그대로 유지 ===
# iflex.xxx=...

application.yml

yaml
# Firo 설정
firo:
  enabled: true
  db:
    type: mybatis  # iflex와 동일
  directory:
    base-dir: /var/uploads/
    tmp-dir: /var/uploads/temp/
  cdn-url: https://cdn.yourdomain.com/
  api-prefix: /api/firo

# Spring Boot 설정
spring:
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB

# iflex 기존 설정은 그대로 유지
# iflex:
#   xxx: ...

application-dev.properties (개발 환경)

properties
# Firo 개발 환경 설정
firo.enabled=true
firo.db.type=mybatis
firo.directory.base-dir=./uploads/
firo.directory.tmp-dir=./uploads/temp/

# 디버깅 로깅
logging.level.com.unvus.firo=DEBUG
logging.level.com.unvus.firo.mybatis.mapper=DEBUG

application-dev.yml (개발 환경)

yaml
# Firo 개발 환경 설정
firo:
  enabled: true
  db:
    type: mybatis
  directory:
    base-dir: ./uploads/
    tmp-dir: ./uploads/temp/

logging:
  level:
    com.unvus.firo: DEBUG
    com.unvus.firo.mybatis.mapper: DEBUG

3단계: 데이터베이스 테이블 생성

MySQL DDL

sql
-- Firo 파일 관리 테이블 (iflex 테이블 프리픽스에 맞춰 조정)
CREATE TABLE attach (
    attach_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    attach_ref_domain VARCHAR(50) NOT NULL,
    attach_ref_key BIGINT NOT NULL,
    attach_ref_category VARCHAR(50) NOT NULL DEFAULT 'default',
    attach_display_name VARCHAR(255) NOT NULL,
    attach_saved_name VARCHAR(255) NOT NULL,
    attach_saved_dir VARCHAR(500) NOT NULL,
    attach_file_type VARCHAR(100),
    attach_file_size BIGINT,
    attach_deleted BOOLEAN DEFAULT FALSE,
    attach_ext VARCHAR(100),
    attach_created_by BIGINT,
    attach_created_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_attach_ref (attach_ref_domain, attach_ref_key, attach_ref_category),
    INDEX idx_attach_deleted (attach_deleted)
);

프리픽스가 있는 경우

properties
# iflex 테이블 프리픽스 설정 (예: ha_attach)
firo.database.table-prefix=ha_

4단계: Spring Configuration 설정

FiroIflex통합Configuration.java

java
package com.yourproject.config;

import com.unvus.firo.core.module.service.FiroRegistry;
import com.unvus.firo.core.module.service.domain.FiroDomain;
import com.unvus.firo.core.module.service.domain.FiroCategory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Slf4j
@Configuration
public class FiroIflexIntegrationConfiguration {

    @PostConstruct
    public void setupFiroDomains() {
        // iflex 프로젝트에 맞는 도메인 설정
        
        // 사용자 프로필 이미지
        FiroDomain userDomain = FiroDomain.builder("user")
            .build();
        userDomain.addCategory("profile");  // 프로필 사진
        userDomain.addCategory("avatar");   // 아바타
        FiroRegistry.add(userDomain);
        
        // 게시판 첨부파일
        FiroDomain boardDomain = FiroDomain.builder("board")
            .build();
        boardDomain.addCategory("attachment"); // 일반 첨부파일
        boardDomain.addCategory("image");      // 이미지 첨부파일
        FiroRegistry.add(boardDomain);
        
        // 공통 파일
        FiroDomain commonDomain = FiroDomain.builder("common")
            .build();
        commonDomain.addCategory("temp");      // 임시 파일
        commonDomain.addCategory("document");  // 문서 파일
        FiroRegistry.add(commonDomain);
        
        log.info("Firo domains configured for iflex integration");
    }
}

5단계: 통합 테스트

기본 연동 테스트

java
package com.yourproject.service;

import com.unvus.firo.core.module.service.FiroService;
import com.unvus.firo.core.module.service.domain.FiroFile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service
public class FileUploadService {

    @Autowired
    private FiroService firoService;
    
    public String uploadUserProfile(Long userId, MultipartFile file) {
        try {
            // 임시 업로드
            String tempUuid = firoService.uploadTemp(file, "user", "profile");
            
            // 실제 저장 (사용자 ID를 refKey로 사용)
            String savedPath = firoService.save(userId, tempUuid, "user", "profile");
            
            return savedPath;
        } catch (Exception e) {
            throw new RuntimeException("파일 업로드 실패", e);
        }
    }
    
    public List<FiroFile> getUserProfileImages(Long userId) {
        return firoService.listAttachByRef("user", userId, "profile");
    }
}

6단계: API 엔드포인트 사용

Firo REST API 사용 예시

java
package com.yourproject.service;

import com.unvus.firo.core.module.service.FiroService;
import com.unvus.firo.core.module.service.domain.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service
public class UserFileService {

    @Autowired
    private FiroService firoService;
    
    public String uploadUserProfile(Long userId, MultipartFile file) {
        try {
            // 1. 임시 업로드
            FiroCategory category = FiroRegistry.get("user", "profile");
            String tempUuid = firoService.uploadTemp(file, category, null, true);
            
            // 2. AttachBag 생성 및 저장
            AttachBag bag = new AttachBag("user");
            FiroFile firoFile = new FiroFile();
            firoFile.setDisplayName(file.getOriginalFilename());
            firoFile.setSavedName(tempUuid);
            firoFile.setFileType(file.getContentType());
            firoFile.setFileSize(file.getSize());
            
            bag.add("profile", firoFile);
            
            List<FiroFile> savedFiles = firoService.save(
                userId, bag, LocalDateTime.now(), false, false
            );
            
            return savedFiles.get(0).getSavedDir() + savedFiles.get(0).getSavedName();
        } catch (Exception e) {
            throw new RuntimeException("파일 업로드 실패", e);
        }
    }
    
    public AttachBag getUserFiles(Long userId) {
        return firoService.getAttachBagByRef("user", userId, null);
    }
}

또는 Firo의 내장 REST API 직접 사용

bash
# 임시 파일 업로드
curl -X POST \
  http://localhost:8080/api/firo/attach/tmp \
  -F 'file=@profile.jpg' \
  -F 'refDomain=user' \
  -F 'refCategory=profile'

# AttachBag 저장
curl -X POST \
  http://localhost:8080/api/firo/attach/user/123 \
  -H 'Content-Type: application/json' \
  -d '{
    "categories": {
      "profile": [{
        "displayName": "profile.jpg",
        "savedName": "temp-uuid-123.jpg",
        "fileType": "image/jpeg",
        "fileSize": 102400
      }]
    },
    "cleanDomain": false,
    "keepTemp": false
  }'

# AttachBag 조회
curl -X GET http://localhost:8080/api/firo/attach/user/123

## 주의사항 및 베스트 프랙티스

### 1. SqlSessionFactory 통합
- **동일한 SqlSessionFactory 사용**: iflex와 Firo가 같은 SqlSessionFactory를 공유해야 함
- **Mapper 스캔 설정**: `@MapperScans`를 사용하여 모든 Mapper를 등록
- **Type Alias 관리**: 사용자가 직접 Type Alias를 등록해야 함

### 2. MyBatis Type Alias 관리
- **수동 등록 필요**: Firo Type Alias를 수동으로 등록해야 함
- **충돌 방지**: 서로 다른 패키지의 동명 클래스 주의
- **테스트**: 기존 iflex MyBatis 쿼리가 정상 동작하는지 확인

### 3. 테이블 네이밍 통일
```properties
# iflex 테이블 명명 규칙에 맞춤
firo.database.table-prefix=ha_  # ha_attach 테이블 사용

4. 파일 저장소 설정

properties
# 프로덕션에서는 클라우드 스토리지 사용 권장
firo.s3.access-key=${AWS_ACCESS_KEY_ID}
firo.s3.secret-key=${AWS_SECRET_ACCESS_KEY}
firo.s3.bucket=iflex-files
firo.s3.region=ap-northeast-2

5. 성능 최적화

properties
# 파일 업로드 성능 최적화
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB
firo.directory.tmp-dir=/var/tmp/firo/  # 충분한 디스크 공간 확보

6. 보안 설정

java
@Configuration
public class FiroSecurityConfiguration {
    
    @Bean
    public SecureAccessFunc secureAccessFunc() {
        return (request, firoFile) -> {
            // iflex 보안 정책에 맞는 접근 제어 구현
            // 예: 사용자 권한 확인, 파일 소유권 검증 등
            return true; // 또는 실제 보안 로직
        };
    }
}

트러블슈팅

자주 발생하는 문제들

  1. Mapper 인식 실패:

    • @MapperScan에 Firo Mapper 패키지가 올바르게 등록되어 있는지 확인
    • basePackages = "com.unvus.firo.mybatis.mapper"
  2. Type Alias 오류:

    • Firo Type Alias가 올바르게 등록되어 있는지 확인
    • "Result Maps collection does not contain value" 오류 시 Type Alias 등록 상태 확인
  3. SqlSessionFactory 충돌:

    • 모든 Mapper가 동일한 sqlSessionFactoryRef를 사용하는지 확인
  4. 테이블 없음: DDL 실행 및 테이블 프리픽스 확인

  5. 파일 업로드 실패: 디렉터리 권한 및 크기 제한 확인

디버깅 로깅

properties
# Firo 관련 로깅
logging.level.com.unvus.firo=DEBUG
logging.level.com.unvus.firo.mybatis.mapper=DEBUG

# iflex 관련 로깅 (기존 설정 유지)
logging.level.com.yourproject=DEBUG

지원 및 문의

추가 지원이 필요한 경우:

  1. 문서 참고: 트러블슈팅 가이드
  2. 로그 분석: DEBUG 레벨 로그로 상세 정보 확인
  3. 설정 검증: 모든 필수 설정이 올바른지 확인
  4. GitHub Issues: 구체적인 오류 정보와 함께 문의

마이그레이션 체크리스트

  • [ ] 의존성 추가 완료
  • [ ] application.properties 설정 완료
  • [ ] 데이터베이스 테이블 생성 완료
  • [ ] Spring Configuration 설정 완료
  • [ ] 기본 파일 업로드 테스트 완료
  • [ ] 기존 iflex 기능 정상 동작 확인
  • [ ] MyBatis 쿼리 정상 실행 확인
  • [ ] 프로덕션 환경 설정 완료