[Spring] 비속어 필터 적용하기

Dunkin00·2023년 6월 28일
1
post-thumbnail

최종 프로젝트의 MVP 구현 이후 유저 테스트 진행 첫날,
옆 조에서 비속어로 잔뜩 두른 사용자의 테러 소식이 들려왔습니다.

프로젝트에 비속어 필터 적용기 시작합니다.

BadWords interface

public interface BadWords {
    String[] badWords = {"나쁜말","반말하지마!","너정말나쁜아이구나"}; // ①
    String[] delimiters = { " ", ",", ".", "!", "?", "@", "1"}; // ②
    String substituteValue = "*"; // ③
}

① 금지된 단어들의 배열입니다. 여기에 "나쁜말"을 추가하면 해당 단어를 필터링 합니다.
② 구분자들의 배열입니다. 구분자를 사용하여 금지된 단어의 변형, 사용을 방지합니다.
(ex."나1뻐", "나쁜~말")
③ 필터링 되었을때 사용할 대체 문자 입니다.

BadWordFiltering class

@Component
public class BadWordFiltering implements BadWords {
	private final Set<String> badWordsSet = new HashSet<>(List.of(badWords)); // ①
    private final Map<String, Pattern> badWordPatterns = new HashMap<>(); // ②

① badwords 배열의 모든 값을 Set 구조에 저장합니다. Set은 중복을 허용하지 않는 데이터 구조로, 각 단어가 한 번만 저장되는 것을 보장합니다.
② Map 구조를 사용하여 단어(badword)를 Key로, 단어와 대응하는 정규식 패턴을 미리 컴파일하여 Value로 저장합니다. 이로써 단어를 검증할때 매번 정규식 패턴을 새로 만들지 않고 빠르게 찾아 사용할 수 있습니다.

  • compileBadWordPatterns()
@PostConstruct // ①
    public void compileBadWordPatterns() {
        String patternText = buildPatternText();

        for (String word : badWordsSet) {
            String[] chars = word.split("");
            badWordPatterns.put(word, Pattern.compile(String.join(patternText, chars))); // ②
        }
    }

① @PostConstruct는 Java에서 제공하는 어노테이션 중 하나로, 스프링 빈이 생성되고 의존성 주입이 완료된 직후에 호출되는 초기화 콜백 메서드를 지정하는데 사용됩니다.
해당 코드에선 compileBadWordPatterns() 메소드를 자동으로 호출하여 단어와, 컴파일한 정규식 패턴을 Map 구조에 저장하기 위해 사용하였습니다.
② Pattern.compile()은 String 값으로 들어온 정규식을 Pattern 객체로 변환시켜줍니다.

  • buildPatternText()
    private String buildPatternText() {
        StringBuilder delimiterBuilder = new StringBuilder("["); // ①
        for (String delimiter : delimiters) {
            delimiterBuilder.append(Pattern.quote(delimiter)); // ②
        }
        delimiterBuilder.append("]*"); 
        return delimiterBuilder.toString();
    }

① 정의된 구분자들을 정규 표현식 처리에 사용할 수 있도록 패턴을 생성합니다.
정규 표현식의 []는 문자 클래스로, '괄호안의 문자들과 매치'라는 의미를 가집니다.
예를들어, 정규표현식 [car]는 "c","a","r" 중 일치하는 것을 찾습니다.
② Pattern.quote()는 Java 정규 표현식 처리에 사용되는 메서드로 정규 표현식 중 특별한 의미를 가진 "*", "["와 같은 문자의 이스케이프 처리에 사용됩니다. -> "\*", "\["

  • checkBadWord()
	public boolean checkBadWord(String input) {
        for (Pattern pattern : badWordPatterns.values()) { // ①
            if (pattern.matcher(input).find()) { // ②
                return true;
            }
        }
        return false;
    }

① Map에 저장해둔 미리 컴파일된 Pattern 객체를 불러옵니다.
② 사용된 matcher()는 String.join()으로 생성된 정규 표현식에 대해 input값과 매칭되는 Matcher 객체를 반환하고 find(), 존재하면 true를 리턴합니다.

  • change()
	public String change(String text) {
        for (Map.Entry<String, Pattern> entry : badWordPatterns.entrySet()) { // ①
            String word = entry.getKey();
            Pattern pattern = entry.getValue();
            if (word.length() == 1){
                text = text.replace(word, substituteValue);
            }
            text = pattern.matcher(text).replaceAll(matchedWord ->
                            substituteValue.repeat(matchedWord.group().length())); // ②
        }
        return text;
    }

Map.Entry와 entrySet() 알아보기
② Matcher 객체의 group() 매치된 부분을 반환하고, length() 그 길이만큼 대체 문자로 replaceAll() 변환합니다.

확인

patternBuilder로 생성된 패턴과 컴파일 되어 Map에 저장되어 있는 패턴을 확인 할 수 있다.

정규식 패턴 확인 - find(), group()의 결과를 확인할 수 있다.
테스트 사이트
https://www.regexplanet.com/advanced/java/index.html

제가 도움 받은 것처럼 이 글이 누군가에게 도움이 되길 바랍니다.

📚 참고

정규 표현식

Pattern

profile
🌊 파도가 칠 때는 서핑을

0개의 댓글