mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-22 16:21:15 +08:00
feat: limit daily AI formatting usage
This commit is contained in:
@@ -4,6 +4,7 @@ import com.openisle.model.PasswordStrength;
|
||||
import com.openisle.model.PublishMode;
|
||||
import com.openisle.service.PasswordValidator;
|
||||
import com.openisle.service.PostService;
|
||||
import com.openisle.service.AiUsageService;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -14,12 +15,14 @@ import org.springframework.web.bind.annotation.*;
|
||||
public class AdminConfigController {
|
||||
private final PostService postService;
|
||||
private final PasswordValidator passwordValidator;
|
||||
private final AiUsageService aiUsageService;
|
||||
|
||||
@GetMapping
|
||||
public ConfigDto getConfig() {
|
||||
ConfigDto dto = new ConfigDto();
|
||||
dto.setPublishMode(postService.getPublishMode());
|
||||
dto.setPasswordStrength(passwordValidator.getStrength());
|
||||
dto.setAiFormatLimit(aiUsageService.getFormatLimit());
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -31,6 +34,9 @@ public class AdminConfigController {
|
||||
if (dto.getPasswordStrength() != null) {
|
||||
passwordValidator.setStrength(dto.getPasswordStrength());
|
||||
}
|
||||
if (dto.getAiFormatLimit() != null) {
|
||||
aiUsageService.setFormatLimit(dto.getAiFormatLimit());
|
||||
}
|
||||
return getConfig();
|
||||
}
|
||||
|
||||
@@ -38,5 +44,6 @@ public class AdminConfigController {
|
||||
public static class ConfigDto {
|
||||
private PublishMode publishMode;
|
||||
private PasswordStrength passwordStrength;
|
||||
private Integer aiFormatLimit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.openisle.controller;
|
||||
|
||||
import com.openisle.service.OpenAiService;
|
||||
import com.openisle.service.AiUsageService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -16,13 +18,21 @@ import java.util.Map;
|
||||
public class AiController {
|
||||
|
||||
private final OpenAiService openAiService;
|
||||
private final AiUsageService aiUsageService;
|
||||
|
||||
@PostMapping("/format")
|
||||
public ResponseEntity<Map<String, String>> format(@RequestBody Map<String, String> req) {
|
||||
public ResponseEntity<Map<String, String>> format(@RequestBody Map<String, String> req,
|
||||
Authentication auth) {
|
||||
String text = req.get("text");
|
||||
if (text == null) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
int limit = aiUsageService.getFormatLimit();
|
||||
int used = aiUsageService.getCount(auth.getName());
|
||||
if (limit > 0 && used >= limit) {
|
||||
return ResponseEntity.status(429).build();
|
||||
}
|
||||
aiUsageService.incrementAndGetCount(auth.getName());
|
||||
return openAiService.formatMarkdown(text)
|
||||
.map(t -> ResponseEntity.ok(Map.of("content", t)))
|
||||
.orElse(ResponseEntity.status(500).build());
|
||||
|
||||
@@ -25,6 +25,9 @@ public class ConfigController {
|
||||
@Value("${app.captcha.comment-enabled:false}")
|
||||
private boolean commentCaptchaEnabled;
|
||||
|
||||
@Value("${app.ai.format-limit:3}")
|
||||
private int aiFormatLimit;
|
||||
|
||||
@GetMapping("/config")
|
||||
public ConfigResponse getConfig() {
|
||||
ConfigResponse resp = new ConfigResponse();
|
||||
@@ -33,6 +36,7 @@ public class ConfigController {
|
||||
resp.setLoginCaptchaEnabled(loginCaptchaEnabled);
|
||||
resp.setPostCaptchaEnabled(postCaptchaEnabled);
|
||||
resp.setCommentCaptchaEnabled(commentCaptchaEnabled);
|
||||
resp.setAiFormatLimit(aiFormatLimit);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@@ -43,5 +47,6 @@ public class ConfigController {
|
||||
private boolean loginCaptchaEnabled;
|
||||
private boolean postCaptchaEnabled;
|
||||
private boolean commentCaptchaEnabled;
|
||||
private int aiFormatLimit;
|
||||
}
|
||||
}
|
||||
|
||||
31
src/main/java/com/openisle/model/AiFormatUsage.java
Normal file
31
src/main/java/com/openisle/model/AiFormatUsage.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package com.openisle.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/** Daily count of AI markdown formatting usage for a user. */
|
||||
@Entity
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@Table(name = "ai_format_usage",
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "use_date"}))
|
||||
public class AiFormatUsage {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "user_id")
|
||||
private User user;
|
||||
|
||||
@Column(name = "use_date", nullable = false)
|
||||
private LocalDate useDate;
|
||||
|
||||
@Column(nullable = false)
|
||||
private int count;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.openisle.repository;
|
||||
|
||||
import com.openisle.model.AiFormatUsage;
|
||||
import com.openisle.model.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface AiFormatUsageRepository extends JpaRepository<AiFormatUsage, Long> {
|
||||
Optional<AiFormatUsage> findByUserAndUseDate(User user, LocalDate useDate);
|
||||
}
|
||||
54
src/main/java/com/openisle/service/AiUsageService.java
Normal file
54
src/main/java/com/openisle/service/AiUsageService.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package com.openisle.service;
|
||||
|
||||
import com.openisle.model.AiFormatUsage;
|
||||
import com.openisle.model.User;
|
||||
import com.openisle.repository.AiFormatUsageRepository;
|
||||
import com.openisle.repository.UserRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AiUsageService {
|
||||
private final AiFormatUsageRepository usageRepository;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Value("${app.ai.format-limit:3}")
|
||||
private int formatLimit;
|
||||
|
||||
public int getFormatLimit() {
|
||||
return formatLimit;
|
||||
}
|
||||
|
||||
public void setFormatLimit(int formatLimit) {
|
||||
this.formatLimit = formatLimit;
|
||||
}
|
||||
|
||||
public int incrementAndGetCount(String username) {
|
||||
User user = userRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
||||
LocalDate today = LocalDate.now();
|
||||
AiFormatUsage usage = usageRepository.findByUserAndUseDate(user, today)
|
||||
.orElseGet(() -> {
|
||||
AiFormatUsage u = new AiFormatUsage();
|
||||
u.setUser(user);
|
||||
u.setUseDate(today);
|
||||
u.setCount(0);
|
||||
return u;
|
||||
});
|
||||
usage.setCount(usage.getCount() + 1);
|
||||
usageRepository.save(usage);
|
||||
return usage.getCount();
|
||||
}
|
||||
|
||||
public int getCount(String username) {
|
||||
User user = userRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
||||
return usageRepository.findByUserAndUseDate(user, LocalDate.now())
|
||||
.map(AiFormatUsage::getCount)
|
||||
.orElse(0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user