-
Notifications
You must be signed in to change notification settings - Fork 6
[VISION] 분석 일일 사용량 DB 관리 및 인증 UX 전면 개선 #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| package io.github.petty.vision.entity; | ||
|
|
||
| import jakarta.persistence.*; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import lombok.Setter; | ||
| import org.hibernate.annotations.CreationTimestamp; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.time.LocalDateTime; | ||
| import java.util.UUID; | ||
|
|
||
| @Entity | ||
| @Table(name = "vision_usage", | ||
| uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "usage_date"})) | ||
| @Getter | ||
| @Setter | ||
| @NoArgsConstructor | ||
| public class VisionUsage { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.UUID) | ||
| private UUID id; | ||
|
|
||
| @Column(name = "user_id", nullable = false) | ||
| private UUID userId; | ||
|
|
||
| @Column(name = "usage_date", nullable = false) | ||
| private LocalDate usageDate; | ||
|
|
||
| @Column(name = "usage_count", nullable = false) | ||
| private Integer usageCount = 0; | ||
|
|
||
| @Column(name = "daily_limit", nullable = false) | ||
| private Integer dailyLimit = 3; // 기본값 3회 | ||
|
|
||
| @CreationTimestamp | ||
| @Column(name = "created_at") | ||
| private LocalDateTime createdAt; | ||
|
|
||
| @Column(name = "updated_at") | ||
| private LocalDateTime updatedAt; | ||
|
|
||
| public VisionUsage(UUID userId, LocalDate usageDate, Integer dailyLimit) { | ||
| this.userId = userId; | ||
| this.usageDate = usageDate; | ||
| this.dailyLimit = dailyLimit; | ||
| this.usageCount = 0; | ||
| } | ||
|
|
||
| // 사용량 증가 | ||
| public void incrementUsage() { | ||
| this.usageCount++; | ||
| this.updatedAt = LocalDateTime.now(); | ||
| } | ||
|
|
||
| // 사용 가능 여부 확인 | ||
| public boolean canUse() { | ||
| return this.usageCount < this.dailyLimit; | ||
| } | ||
|
|
||
| // 남은 사용량 | ||
| public int getRemainingUsage() { | ||
| return Math.max(0, this.dailyLimit - this.usageCount); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| package io.github.petty.vision.repository; | ||
|
|
||
| import io.github.petty.vision.entity.VisionUsage; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.data.jpa.repository.Modifying; | ||
| import org.springframework.data.jpa.repository.Query; | ||
| import org.springframework.data.repository.query.Param; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.util.Optional; | ||
| import java.util.UUID; | ||
|
|
||
| @Repository | ||
| public interface VisionUsageRepository extends JpaRepository<VisionUsage, UUID> { | ||
|
|
||
| /** | ||
| * 특정 사용자의 특정 날짜 사용량 조회 | ||
| */ | ||
| Optional<VisionUsage> findByUserIdAndUsageDate(UUID userId, LocalDate usageDate); | ||
|
|
||
| /** | ||
| * 오늘 사용량 조회 (편의 메서드) | ||
| */ | ||
| @Query("SELECT v FROM VisionUsage v WHERE v.userId = :userId AND v.usageDate = CURRENT_DATE") | ||
| Optional<VisionUsage> findTodayUsage(@Param("userId") UUID userId); | ||
|
|
||
| /** | ||
| * 특정 사용자의 총 사용량 조회 | ||
| */ | ||
| @Query("SELECT COALESCE(SUM(v.usageCount), 0) FROM VisionUsage v WHERE v.userId = :userId") | ||
| Long getTotalUsageByUser(@Param("userId") UUID userId); | ||
|
|
||
| /** | ||
| * 오래된 사용량 기록 삭제 (30일 이전) | ||
| */ | ||
| @Modifying | ||
| @Query("DELETE FROM VisionUsage v WHERE v.usageDate < :cutoffDate") | ||
| void deleteOldUsageRecords(@Param("cutoffDate") LocalDate cutoffDate); | ||
|
|
||
| /** | ||
| * 특정 날짜 이후 사용량 기록 조회 | ||
| */ | ||
| @Query("SELECT v FROM VisionUsage v WHERE v.userId = :userId AND v.usageDate >= :fromDate ORDER BY v.usageDate DESC") | ||
| java.util.List<VisionUsage> findUsageHistory(@Param("userId") UUID userId, @Param("fromDate") LocalDate fromDate); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,128 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package io.github.petty.vision.service; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import io.github.petty.vision.entity.VisionUsage; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import io.github.petty.vision.repository.VisionUsageRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.beans.factory.annotation.Value; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.scheduling.annotation.Scheduled; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.transaction.annotation.Transactional; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.LocalDate; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.UUID; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Service | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Slf4j | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Transactional(transactionManager = "supabaseTransactionManager") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class VisionUsageService { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final VisionUsageRepository visionUsageRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Value("${vision.daily-limit:3}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private int defaultDailyLimit; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 오늘 사용량 조회 (없으면 새로 생성) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public VisionUsage getTodayUsage(UUID userId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LocalDate today = LocalDate.now(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return visionUsageRepository.findByUserIdAndUsageDate(userId, today) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .orElseGet(() -> createNewUsageRecord(userId, today)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
+@Transactional(transactionManager = "supabaseTransactionManager", readOnly = false)
public VisionUsage getTodayUsage(UUID userId) {
LocalDate today = LocalDate.now();
return visionUsageRepository.findByUserIdAndUsageDate(userId, today)
.orElseGet(() -> createNewUsageRecord(userId, today));
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 사용 가능 여부 확인 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public boolean canUseToday(UUID userId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VisionUsage usage = getTodayUsage(userId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return usage.canUse(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 사용량 증가 (분석 완료 시 호출) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public VisionUsage incrementUsage(UUID userId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VisionUsage usage = getTodayUsage(userId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!usage.canUse()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new IllegalStateException( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String.format("일일 사용 한도(%d회)를 초과했습니다. 현재 사용량: %d회", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| usage.getDailyLimit(), usage.getUsageCount()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| usage.incrementUsage(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VisionUsage saved = visionUsageRepository.save(usage); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.info("Vision 사용량 증가: 사용자={}, 날짜={}, 사용량={}/{}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| userId, usage.getUsageDate(), saved.getUsageCount(), saved.getDailyLimit()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return saved; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+47
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain동시성 이슈: 사용량 증가 시 경합 상태 발생 가능 여러 요청이 동시에 사용량을 증가시킬 때 경합 상태가 발생할 수 있습니다. 데이터베이스 레벨에서 락을 사용하거나 낙관적 잠금을 적용하는 것을 고려하세요. 비관적 잠금을 사용한 해결 방법: public VisionUsage incrementUsage(UUID userId) {
- VisionUsage usage = getTodayUsage(userId);
+ // 비관적 잠금을 사용하여 동시성 문제 해결
+ VisionUsage usage = visionUsageRepository.findByUserIdAndUsageDateWithLock(userId, LocalDate.now())
+ .orElseGet(() -> createNewUsageRecord(userId, LocalDate.now()));🏁 Script executed: #!/bin/bash
# VisionUsage 엔티티에서 버전 필드나 락 메커니즘 확인
ast-grep --pattern $'class VisionUsage {
$$$
}'Length of output: 50 추가 정보 확인: 🏁 Script executed: #!/bin/bash
# 1) VisionUsage 엔티티 클래스 위치 및 정의 확인
rg -n "class VisionUsage" -C 3 src/main/java
# 2) 낙관적 잠금을 위한 @Version 어노테이션 필드 확인
rg -n "@Version" -C 3 src/main/javaLength of output: 1357 저장소 인터페이스에서 잠금 관련 메서드 정의 여부를 확인해 주세요. 🏁 Script executed: #!/bin/bash
# VisionUsageRepository 인터페이스 위치 및 메서드 정의 확인
rg -n "interface VisionUsageRepository" -C 5 src/main/java
# 잠금 어노테이션 또는 잠금 메서드 확인
rg -n "@Lock" -C 5 src/main/java
rg -n "findByUserIdAndUsageDate" -C 5 src/main/javaLength of output: 3636 동시성 보장: VisionUsage 증가 시 잠금 전략 적용 필요 현재 • 엔티티에 낙관적 잠금 추가
수정 예시: // src/main/java/io/github/petty/vision/entity/VisionUsage.java
public class VisionUsage {
+ @Version
+ private Long version;
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
// ...
}// src/main/java/io/github/petty/vision/repository/VisionUsageRepository.java
public interface VisionUsageRepository extends JpaRepository<VisionUsage, UUID> {
- Optional<VisionUsage> findByUserIdAndUsageDate(UUID userId, LocalDate usageDate);
+ @Lock(LockModeType.PESSIMISTIC_WRITE)
+ @Query("SELECT v FROM VisionUsage v WHERE v.userId = :userId AND v.usageDate = CURRENT_DATE")
+ Optional<VisionUsage> findByUserIdAndUsageDate(UUID userId, LocalDate usageDate);
}적용 후 동시성 문제가 해결되는지 확인해 주세요.
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 남은 사용량 조회 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public int getRemainingUsage(UUID userId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VisionUsage usage = getTodayUsage(userId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return usage.getRemainingUsage(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 사용량 기록 생성 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private VisionUsage createNewUsageRecord(UUID userId, LocalDate date) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VisionUsage newUsage = new VisionUsage(userId, date, defaultDailyLimit); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VisionUsage saved = visionUsageRepository.save(newUsage); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.debug("새로운 Vision 사용량 기록 생성: 사용자={}, 날짜={}, 한도={}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| userId, date, defaultDailyLimit); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return saved; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 사용자 사용량 히스토리 조회 (최근 7일) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public List<VisionUsage> getRecentUsageHistory(UUID userId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LocalDate fromDate = LocalDate.now().minusDays(7); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return visionUsageRepository.findUsageHistory(userId, fromDate); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 총 사용량 조회 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public Long getTotalUsage(UUID userId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return visionUsageRepository.getTotalUsageByUser(userId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 오래된 기록 정리 (매일 새벽 2시) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Scheduled(cron = "0 0 2 * * *") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void cleanupOldRecords() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LocalDate cutoffDate = LocalDate.now().minusDays(30); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| visionUsageRepository.deleteOldUsageRecords(cutoffDate); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.info("Vision 사용량 기록 정리 완료: {} 이전 데이터 삭제", cutoffDate); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+105
to
+110
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 스케줄러 메서드에 트랜잭션 및 에러 핸들링 추가 필요 대량 삭제 작업 시 트랜잭션 처리와 에러 핸들링이 필요합니다. +@Transactional(transactionManager = "supabaseTransactionManager")
@Scheduled(cron = "0 0 2 * * *")
public void cleanupOldRecords() {
- LocalDate cutoffDate = LocalDate.now().minusDays(30);
- visionUsageRepository.deleteOldUsageRecords(cutoffDate);
- log.info("Vision 사용량 기록 정리 완료: {} 이전 데이터 삭제", cutoffDate);
+ try {
+ LocalDate cutoffDate = LocalDate.now().minusDays(30);
+ int deletedCount = visionUsageRepository.deleteOldUsageRecords(cutoffDate);
+ log.info("Vision 사용량 기록 정리 완료: {} 이전 데이터 {}건 삭제", cutoffDate, deletedCount);
+ } catch (Exception e) {
+ log.error("Vision 사용량 기록 정리 실패", e);
+ // 모니터링 시스템에 알림 전송 고려
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 관리자용: 사용자 일일 한도 조정 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public VisionUsage updateDailyLimit(UUID userId, int newLimit) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (newLimit < 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new IllegalArgumentException("일일 한도는 0 이상이어야 합니다."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VisionUsage usage = getTodayUsage(userId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| usage.setDailyLimit(newLimit); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.info("Vision 일일 한도 변경: 사용자={}, 기존한도={}, 새한도={}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| userId, usage.getDailyLimit(), newLimit); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return visionUsageRepository.save(usage); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+115
to
+127
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 관리자 권한 검증 및 한도 상한선 추가 필요 관리자 권한 검증과 일일 한도의 상한선 검증이 필요합니다. +@PreAuthorize("hasRole('ADMIN')")
public VisionUsage updateDailyLimit(UUID userId, int newLimit) {
- if (newLimit < 0) {
- throw new IllegalArgumentException("일일 한도는 0 이상이어야 합니다.");
+ if (newLimit < 0 || newLimit > 100) {
+ throw new IllegalArgumentException("일일 한도는 0 이상 100 이하여야 합니다.");
}
VisionUsage usage = getTodayUsage(userId);
+ int oldLimit = usage.getDailyLimit();
usage.setDailyLimit(newLimit);
log.info("Vision 일일 한도 변경: 사용자={}, 기존한도={}, 새한도={}",
- userId, usage.getDailyLimit(), newLimit);
+ userId, oldLimit, newLimit);
return visionUsageRepository.save(usage);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
해상도 제한 완화로 인한 품질 문제 가능성
최소 해상도를 50x50으로 낮추면 분석 품질이 저하될 수 있습니다. Vision API의 최소 요구사항을 확인하세요.
🌐 Web query:
💡 Result:
Both Amazon Rekognition and Google Vision API have specific image resolution requirements to ensure optimal performance.
Amazon Rekognition:
Minimum Image Dimensions: The minimum image dimensions are 80 pixels for both height and width. (docs.aws.amazon.com)
Recommended Resolution: For best results, it's recommended to use images with a resolution of VGA (640x480) or higher. (aws.amazon.com)
Face Detection: To detect a face, it must be no smaller than 40x40 pixels in an image with a resolution of 1920x1080 pixels. (docs.aws.amazon.com)
Google Vision API:
Minimum Image Dimensions: Images should generally be a minimum of 640x480 pixels (about 300,000 pixels) to enable accurate detection. (cloud.google.com)
Recommended Sizes for Specific Features:
It's important to note that while images smaller than these recommended sizes may still work, they could result in lower accuracy. Additionally, images should not exceed 75 million pixels (length x width) for OCR analysis. (cloud.google.com)
In summary, both services accept images with a minimum resolution of 80x80 pixels, but for optimal performance, higher resolutions are recommended.
Citations:
해상도 제한 완화로 인한 API 최소 요구사항 위반
현재 최소 해상도를 50×50으로 설정할 경우,
위 두 가지를 고려하여 비즈니스 요구사항과 분석 품질 저하 가능성을 재검토하고, 최소 해상도를 다음과 같이 상향 조정할 것을 제안합니다.
참고 문서
🤖 Prompt for AI Agents