반응형

API 서버를 구현하다보면 뭐 Header에 토큰을 넣느니, 시스템 구분코드를 넣어서 타 시스템은 튕겨낸다느니 하는 일종의 Autorization 로직을 구현하는 경우가 있다. 내부망 시스템에서 토큰까지는 조금 오버인 것 같고, 다만 허용 시스템 코드를 리스트화 한 후에 허용된 시스템만 API를 태우자, 는 방침에 따라 대충 구현해봤다.

 

구글 제미니가 코드를 기깔나게 짜주었는데, 개발망에서 잘 안돌길래 두시간동안 쌩고생 하면서 고친 썰도 같이 푼다. 시작.

1. HeaderInterceptor.java 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
public class HeaderInterceptor extends HandlerInterceptorAdapter{
    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) 
    throws Exception {
        String headAuth = req.getHeader("callSysCd"== null ? "" : req.getHeader("callSysCd");
        String authCode = "authCode";
        
        if(headAuth.equals(authCode)) {
            return true;
        }else {
            res.sendError(401"Not Authorized");
            return false;
        } 
    }
}
cs

 

HeaderInterceptor를 구현해준다. 대충 이렇게 구현하면 Spring 2.x 버전대에서는 HandlerInterceptorAdapter가 discard 되었다고 경고가 뜰 것이다. 그냥 써도 무관한데, 거슬리면 extends HandlerInterceptorAdapter 대신, implements HandlerInterceptor로 써주면 된다.

header에 callSysCd가 있는지를 판단하고, 있다면 "authCode"라는 문자열과 일치하는지 체크한다. 일치하면 그대로 API를 태우지만, 일치하지 않거나 없다면 401 에러를 return 하게 된다.

당연하게도, 검사문자열을 List 형태로도 구현할 수 있다.

 

2. WebMvcConfig.java 구현

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    HeaderInterceptor headerIntercepter;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(headerIntercepter).addPathPatterns("/test/**");
    }
}
cs

 

이렇게 구현해주고, 하단의 addPathPatterns에 걸러낼 url을 입력받는다. 위와 같이 입력받는 경우, localhost:8080/test/example.do와 같은 하위의 모든 url들은 interceptor에 의해 callSysCd의 여부를 체크할 것이다. 물론, localhost:8080/example.do 는 위의 패턴 url과 다르기에 여부체크를 하지 않는다.

 

이를 응용한다면, login.do는 header를 검사하지 않고 mypage.do와 같은 로그인이 필요한 부분에 대해서만 검사하는 식으로 응용이 가능하다.

 

3. 오류

외부망에서는 개발 순식간에 해두고, 개발망에서 적용했는데 interceptor가 하나도 안먹길래 대체 뭐가 문젠가 하고 뒤져보니 다음과 같이 별거 아닌곳에서 문제가 생겼었다.

나의 경우에는 위의 예시들이 com.test.config/* 디렉토리에 위치해 있었는데, 정작 메인메소드에서는 @SpringBootApplication(scanBasePackages="com.test.common") 이라고 되어있었으니 패키지 스캔이 제대로 안되어서 인터셉터들이 하나도 동작하지 않은것이었다. 로그도 안찍히고 해서 얼마나 당황했던지.

 

아무튼 별 거 아닌것을 찾아서 다행이었다.

반응형
블로그 이미지

김생선

세상의 모든것을 어장관리

,
반응형

요 며칠 쓰는 내용들은 죄다 한 3~4년 전 쯤 플젝하면서 다 설정해둔건데, 역시 사람은 기록해두지 않으면 까먹는건지 뭔지, 기억은 나는데 당최 이게 뭐드라? 하는게 많아 정리하기 시작했다.

 

아무튼, tibero driver와 같은 jar 파일 또는 커스텀 jar 파일들은 당연히 maven 센트럴 repository에 없기 때문에 maven build 또는 maven package를 하면 결과물 jar 파일 내에 패키징되지 않는다. 이를 위한 설정을 밑에 적어두니 나와 같이 혼란스러운 분들에게 도움이 되었으면 좋겠다.

 

tibero6-jdbc.jar 가 없는 상황.

1. pom.xml 설정

pom.xml 에서 다음과 같이 설정해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<properties>
    <java.version>1.8</java.version>
    <webapp.lib>${basedir}/src/main/webapp/WEB-INF/lib</webapp.lib>
</properties>
    
<dependencies>
<!--  Tibero JDBC Project Import Library -->
    <dependency>
        <groupId>com.tmax.tibero</groupId>
        <artifactId>tibero-jdbc</artifactId>
        <version>6</version>
        <scope>system</scope>
        <systemPath>${webapp.lib}/tibero6-jdbc.jar</systemPath>
    </dependency>
</dependencies>
cs

 

<properties> 태그 하위에 <webapp.lib>의 경로를 설정해준다.

이후 webapp.lib은 하위의 외부 라이브러리들의 path를 잡아주는데 활용이 된다.

만약, src/main~ 의 경로가 아닌 다른 경로라면 당연히 수정해줘야한다.

tibero-jdbc 드라이버를 지정해주는데 있어 저런 식으로 해주면 된다. 기존에는 groupId나 artifactId들을 그냥 whatever로 설정했던것 같은데 이번에는 약간 공들여서 설정해보았다.

 

*** 가장 중요한 부분 ***

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <includeSystemScope>true</includeSystemScope>
                <image>
                    <builder>paketobuildpacks/builder-jammy-base:latest</builder>
                </image>
            </configuration>
        </plugin>
    </plugins>
</build>
cs

 

pom.xml의 하단부 configuration 하위에 <includeSystemScope>true</includeSystemScope> 옵션을 꼭 넣어주어야 한다. 이 옵션을 넣어주지 않으면 다른 설정을 다 해줘도 maven package 할 때 죄다 빠져버리게 된다.

 

pom.xml 에서 이렇게 설정해주면 끝난다.

 

2. /src/main/webapp/WEB-INF/lib 하위에 라이브러리 추가

그냥 뭐 어려운 거 없이 folder를 만들어서 위의 디렉토리 구조에 맞게 라이브러리를 넣어주면 된다.

 

 

3. maven package

그냥 돌리면 끝난다.

tibero-jdbc-6.jar 있음ㅋ

 

그럼 jar package 파일에 잘 들어와있는것을 확인할 수 있다. ㅋㅋㅋ

반응형
블로그 이미지

김생선

세상의 모든것을 어장관리

,
반응형

여윽시 압축/압축해제를 구현할 일이 생겼다. 대충 그래서 몇가지 라이브러리를 테스트 해 보니 zip4j가 가장 무난하고 속도도 빨랐다. 사용법도 간편하고.

개인적으로 java.util 에서 구현하는 zip 유틸은 쓰기가 좀 귀찮은데(설정할게 많음) 거기에 fileinputstream으로 구현하니 엄청 느리더라. 100메가 csv 압축하는데 20분 걸리던가. buffer로 구현하면 조금 더 빨라진다는데, 사실 이런걸 직접 구현하는 재미도 있긴 하지만 나보다는 더 똑똑한 사람들이 구현해놓은 라이브러리를 갖다 쓰는게 더 낫지 않을까. 아무튼, 해봤다.

 

1. pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- Spring Boot 2.7.8 에서 설정함 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.8</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
 
<!-- https://mvnrepository.com/artifact/net.lingala.zip4j/zip4j -->
<dependency>
    <groupId>net.lingala.zip4j</groupId>
    <artifactId>zip4j</artifactId>
    <version>2.11.5</version>
</dependency>
cs

 

대충 스프링부트 2.7.8 , zip4j 는 2.11.5를 썼다 이마리야

 

 

2. 파일 압축

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;
 
import java.io.File;
 
public class SingleFileCompressionExample {
    public static void main(String[] args) {
        // 압축대상 파일 경로
        String sourceFilePath = "path/to/source/file.txt";
        // 압축결과 파일 경로
        String zipFilePath = "path/to/output.zip";
        // 암호를 사용할 경우
        String password = "yourPassword";
        
        try {
            ZipFile zipFile = new ZipFile(zipFilePath);
            zipFile.addFile(new File(sourceFilePath), createZipParameters(password));
            
            System.out.println("File compressed successfully!");
        } catch (ZipException e) {
            System.err.println("Error compressing file: " + e.getMessage());
        }
    }
    
    private static ZipParameters createZipParameters(String password) {
        ZipParameters zipParameters = new ZipParameters();
        
        zipParameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
        zipParameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_ULTRA;
        
        // 패스워드를 정하는 경우, 여기에서 zipParameter에 암호 알고리즘 적용함
        if (password != null && !password.isEmpty()) {
            // 암호사용여부
            zipParameters.setEncryptFiles(true);  
            // 암호 알고리즘
            zipParameters.setEncryptionMethod(Zip4jConstants.ENC_METHOD_STANDARD);
            // 적용할 암호
            zipParameters.setPassword(password);
        }
        
        return zipParameters;
    }
}
cs

 

 

3. 파일 압축 해제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;
 
public class FileDecompressionExample {
    public static void main(String[] args) {
 
        // 압축해제 대상파일
        String zipFilePath = "path/to/input.zip";
        
        // 압축해제 대상 디렉토리
        String destDirectory = "path/to/destination";
        
        // 암호가 있는 경우
        String password = "yourPassword"// Set to null if the ZIP file is not encrypted
        
        try {
            ZipFile zipFile = new ZipFile(zipFilePath);
            
            // 암호가 있는 경우를 체크해서 적용함
            if (zipFile.isEncrypted() && password != null) {
                zipFile.setPassword(password);
            }
            
            zipFile.extractAll(destDirectory);
            
            System.out.println("Files extracted successfully!");
        } catch (ZipException e) {
            System.err.println("Error extracting files: " + e.getMessage());
        }
    }
}
 
cs

 

 

chatGPT 에게 물어보고 적용한건데 생각보다 너무 쉽게 잘 짜주어서 놀랐다. 앞으로 종종 써먹어야지.

반응형
블로그 이미지

김생선

세상의 모든것을 어장관리

,
반응형

지금 들어온 프로젝트에서는 application.properties의 DB 접속정보가 모두 암호화되어있다. 생전 이런건 또 처음이라 뒤져보니 역시나 보안을 위한 암호화라고. 뭐 내부망에서만 배포되고 서버내에서만 존재하는 *properties까지 암호화할일은 무엇인가 싶긴 한데 뭐 어디선가는 또 쓰기 마련이지. 아무튼 대략적으로 구현해보고 에러 트라이도 해보고 잘 볶아보았다.

 

1. pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- Spring Boot 2.7.8 에서 설정함 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.8</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
 
<!-- 암복호화 모듈 jasypt -->
<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>3.0.5</version>
</dependency>
 
<!-- 테스트를 수행해본 mariadb -->
<dependency>
    <groupId>org.mariadb.jdbc</groupId>
    <artifactId>mariadb-java-client</artifactId>
    <version>2.7.11</version>
</dependency>
cs

대충 스프링부트 2.7.8에 jasypt 3.0.5 버전을 적용했다, 이마리야

 

2. JasyptConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.test.comm.util;
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties;
@Configuration
@EnableEncryptableProperties
public class JasyptConfig {
        
        @Value("${password}")
        private String passwd;
        
        @Bean("jasyptStringEncryptor")
        public StringEncryptor stringEncryptor() {
            PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
            SimpleStringPBEConfig config = new SimpleStringPBEConfig();
            config.setPassword(passwd); // 암호화키
            config.setAlgorithm("PBEWithMD5AndDES"); // 알고리즘
            config.setKeyObtentionIterations("1000"); // 반복할 해싱 회수
            config.setPoolSize("1"); // 인스턴스 pool
            config.setProviderName("SunJCE");
            config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); // salt 생성 클래스
            config.setIvGeneratorClassName("org.jasypt.iv.NoIvGenerator"); // PBEWithMD5AndDES 사용시에는 이걸 해줘야함
            config.setStringOutputType("base64"); //인코딩 방식
            encryptor.setConfig(config);
            return encryptor;
        }
}
 
cs

 

별로 어려운 것 없이 이거 하나 구현해주면 다 끝난다. 그럼 암호화된 properties의 값들을 복호화해서 쓰게 됨.

자 그럼 데이터를 어떻게 암호화 하냐면,

 

3. Junit Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.test;
 
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
 
public class Test {
 
    @org.junit.Test
    public void test() {
            String url = "jdbc:mariadb://127.0.0.1:3306/mysql";
            String username = "root";
            String password = "1234";
 
            System.out.println(jasyptEncoding(url));
            System.out.println(jasyptEncoding(username));
            System.out.println(jasyptEncoding(password));
        }
 
        public String jasyptEncoding(String value) {
 
            String key = "sssss";
            StandardPBEStringEncryptor pbeEnc = new StandardPBEStringEncryptor();
            pbeEnc.setAlgorithm("PBEWithMD5AndDES");
            pbeEnc.setPassword(key);
            return pbeEnc.encrypt(value);
        }
}
cs

 

난 그냥 junit test로 돌렸는데 아무 클래스 하나 파고 하드코딩(?)같이 해서 암호화 돌려도 상관없다.

algorithm의 경우에는 고객사에서 사용하는 방식으로 적용했고, key는 복호화할때도 써야하는것이니까 잘 보관해야 한다.

쟤네들을 암호화 하면 salt값 포함, 다음과 같은 값이 추출된다.

 

4. application.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
 
#암호화 적용
spring.datasource.url=ENC(VIM2Q6Cr058I98tDUqk9t5AzhbOBtg7prpvmLVo4RMMAvb0vIvzPnggGZlJMOXmH)
spring.datasource.username=ENC(AGGJxx/HldORQzlVuuGTXQ==)
spring.datasource.password=ENC(mCuZLaeAjXHejmp6gW4Yuw==)
 
#암호화 미적용
#spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/mysql
#spring.datasource.username=root
#spring.datasource.password=1234
 
#JasyptConfig 에서 설정한 BeanName
jasypt.encryptor.bean=jasyptStringEncryptor
#JasyptConfig 에서 사용할 passkey
password=sssss
 
jasypt.encryptor.iv-generator-classname=org.jasypt.iv.NoIvGenerator
 
cs

주석에 내용이 다 달려있어서 문제는 없다.

다만, PBEWithMd5AndDES 암호화를 사용하는 경우 datasource.password를 복호화하지 못하는 이슈가 발생하였고, 

JasyptConfig 에 iv-generator-classname을 위와 같이 설정해주어야 한다.

properties에 설정해도 먹히기도 하고, 안먹히기도 한다. 뭐야 이거 양자역학이야?

 

이 이슈는 위의 암호화 방식만 해당되는 내용이므로 다른 방식으로 적용할 경우에는 문제가 없을것이라 판단된다.

반응형
블로그 이미지

김생선

세상의 모든것을 어장관리

,
반응형

몇년만에 밑바닥부터 SpringBoot를 설정해서 개발하는지 모르겠다. 이하는 추후 활용을 위한 개인 기록용.

 

1. pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(중략)
 
<!-- Spring Boot 2.7.8 버전으로 활용 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.8</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
 
<!-- Mybatis Maven -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.2</version>
</dependency>
 
<!-- Maria DB -->
<dependency>
    <groupId>org.mariadb.jdbc</groupId>
    <artifactId>mariadb-java-client</artifactId>
    <version>2.7.11</version>
</dependency>
cs

 

maven dependency는 대략적으로 위와 같이 잡아주었다.

 

2. TestMapper.java / TestService.java / TestServiceImpl.java / TestController.java / TestSql.xml

1
2
3
4
5
6
7
8
9
10
package com.test.common.mapper;
 
import org.apache.ibatis.annotations.Mapper;
 
 
@Mapper
public interface TestMapper {
    public String getSysDate();
}
 
cs

 

 

1
2
3
4
5
6
package com.test.comm.service;
 
public interface TestService {
    String getSysdate();
}
 
cs

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.test.comm.service.impl;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.test.comm.service.TestService;
import com.test.common.mapper.TestMapper;
 
@Service
public class TestServiceImpl implements TestService {
    @Autowired
    TestMapper testMapper;
    
    @Override
    public String getSysdate() {
        String result = testMapper.getSysDate();
        return result;
    }
 
}
 
cs

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.test.comm;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
 
import com.test.comm.service.TestService;
 
@RestController
public class testController {
    @Autowired
    TestService testService;
    
    @PostMapping("/test")
    public String test(@RequestBody String a) {
        String result = "";
        result = testService.getSysdate();
        System.out.println("============= :::: " +result);
        return null;
    }
    
}
 
cs
 
 
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="com.test.common.mapper.TestMapper">
    <select id="getSysDate" resultType="java.lang.String">
        select sysdate() from dual
    </select>
</mapper>
cs

 

 

1
mybatis.mapper-locations=mapper/**/*.xml
cs

 

패키지 구조는 다음과 같다.

com.test.comm.TestController.java

com.test.comm.service.TestService.java

com.test.comm.service.impl.TestServiceImpl.java

com.test.common.mapper.TestMapper.java

src.main.resources.mapper.TestSql.xml

 

application.properties 에는 mybatis.mapper-locations 을 설정해준다.

 

오류가 난 상황으로는 SpringBoot 2.7.8 버전에서 mybatis-spring-boot-starter를 3.x 버전으로 설정해주었더니, TestServiceImpl 에서 @Autowired로 설정한 TestMapper.java를 인식할 수 없다는 오류가 발생했었다.

여러 시행착오 끝에 SpringBoot 2.7.8과 mybatis-spring-boot-starter 3.x과 호환이 안되는것을 인지하고, 현재는 2.x 버전대로 맞추어주었다.

 

에혀 힘들었다.

반응형
블로그 이미지

김생선

세상의 모든것을 어장관리

,
반응형

개발하는데 java 배치로 Windows Powershell을 파라미터 방식으로 호출할 일이 생겼다. 일단 powershell Script 코드는 다음과 같다.

 

1
2
echo 'TEST-1' $test
echo 'TEST-2' $userid
cs

대충 파라미터 두 개( test/userid)를 받아 echo로 찍어주는건데 기타 하위에는 물론 Azure Portal과의 통신이 있긴 하다. 근데 그게 중요한 것은 아니니까.

아무튼, java에서 파워쉘을 호출하는 소스코드는 다음과 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//중략
 
final Runtime rt = Runtime.getRuntime();
 
//powershell.exe 명령어를 통해서, 해당 경로의 ps1 파일을 실행함
String commands = String.format("powershell.exe \"F:\\powershell.ps1  ");
 
Process proc = null;
String s = null;
 
// PowerShell 명령 시도 및 메시지 출력
try {
    proc = rt.exec(commands);
 
    BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream());
    while (( s = stdInput.readLine()) != null ) {
        System.out.println(s);
    }
 
    BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream());
    while (( s = stdError.readLine()) != null ) {
        System.out.println(s);
    }
 
catch (IOException e) {
    e.printStacTrace();
 
}
 
//생략
cs

요기까지는 잘 했는데, 위의 powershell 코드와 같이 parameter를 넘기는 방법을 구글링해보아도 죄다 제각기 방법이 달라서, 츄라이를 해보니 다음과 같은 소스코드로 성공했다.

 

위의 java 소스에서 commands를 다음과 같은 방법으로 변경해주면 된다.

1
2
3
4
5
6
7
//중략
 
String[] arr = {"test_msg" , "test_user"}
//powershell.exe 명령어를 통해서, 해당 경로의 ps1 파일을 실행함
String commands = String.format("powershell.exe \"F:\\powershell.ps1  " + arr[0+ " " + arr[1]);
 
//생략
cs

PowerShell 코드는 다음과 같이 수정해주면 된다.

1
2
3
4
5
$test = $args[0]
$userid = $args[1]
 
echo 'TEST-1' $test
echo 'TEST-2' $userid
cs

java 에서 공백문자열로 구분된 string 값들이 args로 잘 매핑이 된다.

 

테스트를 해보니 해당 ps1(powershell)을 호출할 때 각 String 형태의 parameter(arguments)들을 넘길 때, 그냥 공백문자열로 구분하는 것으로 보인다. 실제로 여러방법으로 테스트 해보니 잘 되기도 하고. 이로써 java로 powershell script를 parameter 포함하여 execute 하는것에 성공했다. 앞으로 근데, 이러한 플젝을 몇번이나 할 지는 모르겠지만 말이다 =_=;;

 

 

java - how pass string array as a parameters to a powershell 

반응형
블로그 이미지

김생선

세상의 모든것을 어장관리

,
반응형

한컴오피스의 한글파일(hwp)에서 텍스트 추출할 일이 생겼다.

대충 뒤져보니 대단하신 분 께서 한글문서 파서 라이브러리를 만드셨는데, 아직까지도 일부기능에 대해 개선작업을 진행중이신 것 같다.

뭐 표 라거나 그림파일 등에 대해서는 정상동작하지 않는 듯 하지만 나는 텍스트만 추출할 것이기 때문에 당장은 문제없이 사용가능할것으로 보인다.

 

자세한 지원범위는 이 분의 깃으로 들어가보면 될 듯.

https://github.com/neolord0/hwplib

 

GitHub - neolord0/hwplib: hwp library for java

hwp library for java. Contribute to neolord0/hwplib development by creating an account on GitHub.

github.com

아무튼, 대충 임포트하고 대충 써보기로 한다. 생각보다 텍스트 추출이 아주 잘 되어서 다행이다.

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/kr.dogfoot/hwplib -->
<dependency>
  <groupId>kr.dogfoot</groupId>
  <artifactId>hwplib</artifactId>
  <version>1.0.1</version>
</dependency>
cs

이렇게 잡아주고,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* hwplib hwp document text extraction example
*/
 
import kr.dogfoot.hwplib.object.HWPFile;
import kr.dogfoot.hwplib.reader.HWPReader;
import kr.dogfoot.hwplib.tool.textextractor.TextExtractMethod;
import kr.dogfoot.hwplib.tool.textextractor.TextExtractor;
 
// 중략
 
HWPFile hwpFile;
String hwpText;
try {
    hwpFile = HWPReader.fromFile("/Users/kimfish/DEV/java_workspace/"+"test.hwp");
    hwpText = TextExtractor.extract(hwpFile, TextExtractMethod.InsertControlTextBetweenParagraphText);
 
    System.out.println("===== hwp text extractor =====");
    System.out.println("hwpText = " + hwpText);
catch (Exception e) {
    e.printStackTrace();
cs

이렇게 쓰면 된다. 개꿀

반응형
블로그 이미지

김생선

세상의 모든것을 어장관리

,
반응형

일전의 포스트와 마찬가지로 pdf 에서도 텍스트를 추출할 일이 생겼다.

당연하겠지만 해당 pdf는 ocr이 된 pdf를 기준으로만 추출이 가능하다.

 

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox -->
<dependency>
  <groupId>org.apache.pdfbox</groupId>
  <artifactId>pdfbox</artifactId>
  <version>2.0.24</version>
</dependency>
cs

이야 1년쯤 전에는 2.0.18 이었는데 그새 버전업했네. 아무튼 maven repository는 이렇게 잡아주고

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* PDFBox library PDF text Extraction Example
*/
 
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
 
// 중략
 
try {
    File file = new File("/Users/kimfish/DEV/java_workspace/"+"/test.pdf");
    PDDocument document;
    document = PDDocument.load(file);
 
    PDFTextStripper s = new PDFTextStripper();
    String content = s.getText(document);
 
    System.out.println("===== docx text extractor =====");
    System.out.println(content); 
catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
cs

이렇게 하면 정상적으로 OCR 처리된 text가 추출된다.

 

 

 

반응형
블로그 이미지

김생선

세상의 모든것을 어장관리

,
반응형

아주 심플하게, 회사에서 해당 문서규격(xls/xlsx/doc/docx)에 대해 텍스트를 추출하는것이 필요했다. 일전에는 잠깐 해보았는데, 정리된 적이 없었고 이번을 계기로 좀 알게된 몇가지 사실들이 있기에 간단하게 기록해본다.

 

일단 메이븐에서 다음과 같이 라이브러리를 잡아준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi-scratchpad</artifactId>
  <version>5.2.2</version>
</dependency>
 
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi</artifactId>
  <version>5.2.2</version>
</dependency>
 
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi-ooxml</artifactId>
  <version>5.2.2</version>
</dependency>
 
cs

 

1. 워드 (doc/docx) 텍스트 추출

워드 2003 이전버전인 doc 와 이후 버전인 docx 를 추출하기 위해서는 두 가지만 기억하면 된다.

HWPFDocument 클래스는 Horrible Word Process Format 의 약자라는 점이며, poi 라이브러리만 상속받는것이 아닌 poi-scratchpad 라이브러리를 같이 상속받아야 한다. 즉, poi dependency만 잡아놓으면 HWPFDocument가 import 되지 않는다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
* Word 2003 doc File Text Extraction Example
*/
 
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
 
// 중략
 
try {
    POIFSFileSystem poiFS = new POIFSFileSystem(
        new FileInputStream("/Users/kimfish/DEV/java_workspace/"+"/test.doc"));
    HWPFDocument hwp = new HWPFDocument(poiFS);
    WordExtractor we = new WordExtractor(hwp);
 
    String[] paragraphs = we.getParagraphText();
    System.out.println("===== doc text extractor ======");
    for (int i = 0; i < paragraphs.length; i++) {
        System.out.println(paragraphs[i]);
    }
 
catch (Exception e) {
    System.out.println(e); 
}
cs

 

docx 파일은 XWPFDocument를 import 받으며, docx의 text추출을 위해서는 poi 및 poi-ooxml 라이브러리에 대한 dependency가 필요하다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* Word 2007 docx File Text Extraction Example
*/
 
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
 
// 중략
 
try(
    FileInputStream fis = new FileInputStream
            ("/Users/kimfish/DEV/py_workspace/"+"/test.docx")) {  
    XWPFDocument file   = new XWPFDocument(OPCPackage.open(fis));  
    XWPFWordExtractor ext = new XWPFWordExtractor(file);  
 
    System.out.println("===== docx text extractor ======");
    System.out.println(ext.getText());  
}catch(Exception e) {  
    System.out.println(e);  
}  
cs

 

2. 엑셀 (xls/xlsx) 텍스트 추출

엑셀은 고려할 점이 상당히 많은데 각 셀/로우 별 텍스트 및 쉬트도 있는데다가 수식까지 가지고 있다. 현재는 샘플 토이 프로젝트라 수식이나 셀/로우 구분없이 텍스트만 가져오기를 수행해보았는데 일단 첫 쉬트에서는 잘 가져오고 있는듯. 좀 더 정교한 작업이 필요할 경우엔 공식 document 가 필요할것으로 보인다.

 

엑셀 2003 이전버전인 xls 같은 경우에는 poi Dependency 하나로도 잘 작동한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* Excel 2003 xls File Text Extraction Example
*/
 
import org.apache.poi.hssf.extractor.ExcelExtractor;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 
// 중략
 
try (
    FileInputStream fis = new FileInputStream("/Users/kimfish/DEV/java_workspace/"+"/test.xls")) {  
    
    String result = "";
 
    HSSFWorkbook wb = new HSSFWorkbook(fis);
    ExcelExtractor ee = new ExcelExtractor(wb);
    result = ee.getText();    
 
    System.out.println("===== xls text extractor =====");
    System.out.println(result);    
catch (Exception e) {
    //TODO: handle exception
}
cs

 

하지만 엑셀 2007 버전인 xlsx 같은 경우에는 워드2007과 마찬가지로 poi / poi-ooxml dependency 둘 다 필요하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
* Excel 2007 xlsx File Text Extraction Example
*/
 
import org.apache.poi.xssf.extractor.XSSFExcelExtractor;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 
// 중략
 
try (
    
    FileInputStream fis = new FileInputStream("/Users/kimfish/DEV/py_workspace/"+"/test.xlsx")) {  
                
    // Workbook
    XSSFWorkbook wb = new XSSFWorkbook(fis);
            
    // Text Extraction
    XSSFExcelExtractor extractor = new XSSFExcelExtractor(wb);
    extractor.setFormulasNotResults(true);
    extractor.setIncludeSheetNames(false);
    
    System.out.println("===== xlsx text extractor =====");
    System.out.println( extractor.getText() );
catch (Exception e) {
    //TODO: handle exception
}
cs

 

일단은 이렇게 해서 정상적으로 출력되는것을 확인함. 어휴 poi 하나로 다 해놓든가 의존성을 걸어두던가. 왜 poi / poi-ooxml / poi-scratchpad 이렇게 세 개가 죄다 쪼개져있는지원;;

 

정리

xlsx - poi / poi-ooxml

xls - poi

docx - poi / poi-ooxml

doc - poi / poi-scratchpad

반응형

'어장 Develop > 어장 JAVA' 카테고리의 다른 글

[hwplib] hwplib을 이용한 한글파일 텍스트 추출  (1) 2022.07.06
[PDFBox] pdf 텍스트 추출  (0) 2022.07.05
[Tomcat 9.x] JNDI설정  (0) 2022.02.21
[PDFBox] java Image to PDF  (0) 2021.08.24
[xml] RestAPI XML Return (2)  (0) 2021.07.06
블로그 이미지

김생선

세상의 모든것을 어장관리

,
반응형

파이썬 개발을 하면서 가장 헷갈리는 개념이 디렉토리 참조법이다. 자바에서는 그냥 대충 import package.directory.fileName; 과 같은 형식으로 지정해주면 바로 가져다 쓸 수 있는데 반해, 파이썬은 그렇지 않은가보다.

 

아무튼 세 가지의 케이스에 대해 정리/적용해보았고 그 내용은 다음과 같다.

 

패키지 구성은 다음과 같은 형식으로 되어있다고 가정한다.

project
⌞ A_folder
   - a_file_1.py
   - a_file_2.py
 B_folder
   - b_file_1.py
   ⌞ C_folder
       - c_file_1.py
   ⌞ D_folder
       - d_file_1.py

1. 동일 경로상의 import

A_folder 내부의 a_file_1.py 에서 a_file_2.py 파일을 import 하기위해서는 다음과 같이 작성한다.

1
from . import a_file_2.py
cs

또는

1
import a_file_2.py
cs

이거는 쉽다.

 

2. 하위 폴더의 import 

B_folder 내부의 b_file_1.py 에서 C_folder 내부의 c_file_1.py를 import 하기 위해서는 다음과 같이 작성한다.

1
from C_folder import c_file_1.py
cs

이정도는 크게 어렵지 않다.

 

3. 상위 폴더의 import

여기서부터 많이 헷갈리게 된다.

C_folder 내부의 c_file_1.py 에서 D_folder 내부의 d_file_1.py를 import 하기 위해서는 꽤 여러가지 방법이 있으나, 일단 내가 해결한 방법을 위주로 작성해본다.

1
2
3
4
import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
from D_folder import d_file_1.py
cs

여기에서 os.path.dirname(os.path.abspath( 는 한단계 상위를 의미하며, C_folder의 한단계 상위인 B_folder로 이동함을 의미한다. B_folder 내부에 D_folder가 위치해있으므로, from d_folder 을 해주는 것.

 

마찬가지로 c_file_1.py 에서 A_folder 를 참조하기 위해서는 os.path.dirname(os.path.abspath를 두 번 작성해주면 될 것이다.

 

 

java를 하다와서 그런지, from~ 쪽도 잘 이해가 안갔고 __file__과 같은 옵션네이밍(?)의 정의도 익숙하지 않다. 일단은 해결했으니까, 다음 개발로 넘어가야지...

반응형
블로그 이미지

김생선

세상의 모든것을 어장관리

,