feat: 회원가입 제한·봇차단(허니팟/레이트리밋)·카드 자동인식 보정
- 관리자 회원가입 허용 토글(app_setting), 공개 GET /auth/signup-enabled - 회원가입 봇차단: 허니팟(website) + IP 레이트리밋(Redis, 1h 5회) - 카드 알림: 현금 오선택 보정(카드 양방향 매칭+단일카드 자동), 광고 푸시 차단(승인신호 없는 광고성 표현 무시) - @MapperScan 에 admin.mapper 추가 - account.sql: 매 기동 wallet MODIFY 제거(라이브 락 위험) — CREATE 정의에 255 반영됨 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
fb94df8112
commit
c05be0880c
@@ -260,16 +260,27 @@ public class AccountService {
|
||||
return toResponse(mapper.findByIdAndMember(id, memberId));
|
||||
}
|
||||
|
||||
/** 카드사명으로 등록된 CARD 지갑 id 찾기 (issuer/name 부분일치) */
|
||||
/** 카드사명으로 등록된 CARD 지갑 id 찾기 (issuer/name 양방향 부분일치). 미매칭이면 카드가 1개뿐일 때 그 카드로. */
|
||||
private Long matchCardWalletId(String issuer, Long memberId) {
|
||||
if (issuer == null) return null;
|
||||
return walletMapper.findByMember(memberId).stream()
|
||||
List<Wallet> cards = walletMapper.findByMember(memberId).stream()
|
||||
.filter(w -> "CARD".equals(w.getType()))
|
||||
.filter(w -> (w.getIssuer() != null && w.getIssuer().contains(issuer))
|
||||
|| (w.getName() != null && w.getName().contains(issuer)))
|
||||
.map(Wallet::getId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
.toList();
|
||||
if (issuer != null) {
|
||||
Long matched = cards.stream()
|
||||
.filter(w -> overlaps(w.getIssuer(), issuer) || overlaps(w.getName(), issuer))
|
||||
.map(Wallet::getId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (matched != null) return matched;
|
||||
}
|
||||
// 카드사 미감지/미매칭이라도 등록된 카드가 정확히 1개면 그 카드로 기본 매칭
|
||||
return cards.size() == 1 ? cards.get(0).getId() : null;
|
||||
}
|
||||
|
||||
/** 양방향 부분일치 (KB국민 ↔ 국민 등) */
|
||||
private boolean overlaps(String walletField, String issuer) {
|
||||
if (walletField == null || issuer == null) return false;
|
||||
return walletField.contains(issuer) || issuer.contains(walletField);
|
||||
}
|
||||
|
||||
/* ===================== 상환/납부 ===================== */
|
||||
|
||||
@@ -26,6 +26,10 @@ public class CardNotificationParser {
|
||||
private static final Pattern AMOUNT = Pattern.compile("([0-9]{1,3}(?:,[0-9]{3})+|[0-9]{4,})\\s*원");
|
||||
private static final Pattern DATE_MD = Pattern.compile("(\\d{1,2})[./](\\d{1,2})");
|
||||
|
||||
// 광고/이벤트 푸시에 흔한 표현 — 강한 승인신호(승인/출금)가 없으면 결제로 보지 않는다.
|
||||
private static final Pattern AD_HINT = Pattern.compile(
|
||||
"이벤트|광고|혜택|쿠폰|할인|적립|포인트|캐시백|마일리지|리워드|당첨|응모|추첨|프로모션|배너|무료|증정|모집");
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
public static class Parsed {
|
||||
@@ -49,6 +53,11 @@ public class CardNotificationParser {
|
||||
if (!am.find() || (!approved && !canceled)) {
|
||||
return notCard();
|
||||
}
|
||||
// 광고/이벤트 차단: 강한 승인신호(승인/출금/취소/환불)가 없는데 광고성 표현이 있으면 결제 아님
|
||||
boolean strongSignal = raw.contains("승인") || raw.contains("출금") || canceled;
|
||||
if (!strongSignal && AD_HINT.matcher(raw).find()) {
|
||||
return notCard();
|
||||
}
|
||||
long amount = Long.parseLong(am.group(1).replace(",", ""));
|
||||
|
||||
String issuer = detectIssuer(raw);
|
||||
|
||||
Reference in New Issue
Block a user