Merge pull request #32 from SlimusMinus/fix-image_moderation
fixed image moderation and dell redis config
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
package com.krylov.refound.ai.client;
|
package com.krylov.refound.ai.client;
|
||||||
|
|
||||||
import com.krylov.refound.ai.dto.ImageModerationResponse;
|
import com.krylov.refound.ai.dto.ImageModerationResponse;
|
||||||
import com.krylov.refound.ai.dto.ModerationResponse;
|
|
||||||
import com.krylov.refound.ai.exception.AiServerException;
|
import com.krylov.refound.ai.exception.AiServerException;
|
||||||
import com.krylov.refound.ai.exception.AiUnavailableException;
|
import com.krylov.refound.ai.exception.AiUnavailableException;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -29,12 +28,16 @@ public class ImageModerationClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Recover
|
@Recover
|
||||||
public ModerationResponse recover(AiUnavailableException e, String text) {
|
public ImageModerationResponse recover(AiUnavailableException e, MultipartFile file) {
|
||||||
log.error("AI ImageModeration unavailable after retries", e);
|
log.error("AI ImageModeration unavailable after retries", e);
|
||||||
|
|
||||||
return ModerationResponse.builder()
|
return null;
|
||||||
.approved(false)
|
}
|
||||||
.reason("AI_UNAVAILABLE")
|
|
||||||
.build();
|
@Recover
|
||||||
|
public ImageModerationResponse recover(AiServerException e, MultipartFile file) {
|
||||||
|
log.error("AI ImageModeration server error after retries", e);
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,13 @@ public class TextModerationClient {
|
|||||||
public ModerationResponse recover(AiUnavailableException e, String text) {
|
public ModerationResponse recover(AiUnavailableException e, String text) {
|
||||||
log.error("AI TextModeration unavailable after retries", e);
|
log.error("AI TextModeration unavailable after retries", e);
|
||||||
|
|
||||||
return ModerationResponse.builder()
|
return null;
|
||||||
.approved(false)
|
}
|
||||||
.reason("AI_UNAVAILABLE")
|
|
||||||
.build();
|
@Recover
|
||||||
|
public ModerationResponse recover(AiServerException e, String text) {
|
||||||
|
log.error("AI TextModeration server error after retries", e);
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class ContentModerationFacade {
|
|||||||
|
|
||||||
private final ContentModerationPipeline pipeline;
|
private final ContentModerationPipeline pipeline;
|
||||||
|
|
||||||
public void moderate(String title, String description, List<MultipartFile> images) {
|
public Boolean moderate(String title, String description, List<MultipartFile> images) {
|
||||||
log.info("moderate ContentModerationFacade get");
|
log.info("moderate ContentModerationFacade get");
|
||||||
ModerationContext context =
|
ModerationContext context =
|
||||||
ModerationContext.builder()
|
ModerationContext.builder()
|
||||||
@@ -24,7 +24,7 @@ public class ContentModerationFacade {
|
|||||||
.images(images)
|
.images(images)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
pipeline.execute(context);
|
return pipeline.execute(context);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,15 @@ public class ContentModerationPipeline {
|
|||||||
|
|
||||||
private final List<ModerationProcessor> processors;
|
private final List<ModerationProcessor> processors;
|
||||||
|
|
||||||
public void execute(ModerationContext context) {
|
public Boolean execute(ModerationContext context) {
|
||||||
log.info("execute ContentModerationPipeline");
|
log.info("execute ContentModerationPipeline");
|
||||||
for (ModerationProcessor processor : processors) {
|
for (ModerationProcessor processor : processors) {
|
||||||
processor.process(context);
|
Boolean approved = processor.process(context);
|
||||||
|
if (approved == null) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Boolean.TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ public class ImageModerationProcessor implements ModerationProcessor {
|
|||||||
private final ImageModerationClient client;
|
private final ImageModerationClient client;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void process(ModerationContext context) {
|
public Boolean process(ModerationContext context) {
|
||||||
List<MultipartFile> images = context.getImages();
|
List<MultipartFile> images = context.getImages();
|
||||||
|
|
||||||
if (images == null || images.isEmpty()) {
|
if (images == null || images.isEmpty()) {
|
||||||
return;
|
return Boolean.TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (MultipartFile image : images) {
|
for (MultipartFile image : images) {
|
||||||
@@ -34,6 +34,10 @@ public class ImageModerationProcessor implements ModerationProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImageModerationResponse response = client.moderate(image);
|
ImageModerationResponse response = client.moderate(image);
|
||||||
|
if (response == null) {
|
||||||
|
log.warn("AI ImageModeration вернул null, пропускаем модерацию изображения");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
log.info("Image moderation result approved={}, score={}, reason={}, labels={}",
|
log.info("Image moderation result approved={}, score={}, reason={}, labels={}",
|
||||||
response.getApproved(),
|
response.getApproved(),
|
||||||
@@ -42,10 +46,12 @@ public class ImageModerationProcessor implements ModerationProcessor {
|
|||||||
response.getDetectedLabels()
|
response.getDetectedLabels()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.getApproved()) {
|
if (!Boolean.TRUE.equals(response.getApproved())) {
|
||||||
throw new ContentBlockedException(response.getReason(), response.getDetectedLabels());
|
throw new ContentBlockedException(response.getReason(), response.getDetectedLabels());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Boolean.TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ package com.krylov.refound.ai.pipeline;
|
|||||||
|
|
||||||
public interface ModerationProcessor {
|
public interface ModerationProcessor {
|
||||||
|
|
||||||
void process(ModerationContext context);
|
Boolean process(ModerationContext context);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class TextModerationProcessor implements ModerationProcessor {
|
|||||||
private final TextModerationClient client;
|
private final TextModerationClient client;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void process(ModerationContext context) {
|
public Boolean process(ModerationContext context) {
|
||||||
log.info("process TextModerationProcessor for context {}", context);
|
log.info("process TextModerationProcessor for context {}", context);
|
||||||
String text = buildText(
|
String text = buildText(
|
||||||
context.getTitle(),
|
context.getTitle(),
|
||||||
@@ -25,12 +25,18 @@ public class TextModerationProcessor implements ModerationProcessor {
|
|||||||
);
|
);
|
||||||
|
|
||||||
ModerationResponse response = client.moderate(text);
|
ModerationResponse response = client.moderate(text);
|
||||||
|
if (response == null) {
|
||||||
|
log.warn("AI TextModeration вернул null, пропускаем модерацию текста");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
log.info("AI moderation result approved={}, score={}, reason={}", response.getApproved(), response.getScore(), response.getReason());
|
log.info("AI moderation result approved={}, score={}, reason={}", response.getApproved(), response.getScore(), response.getReason());
|
||||||
|
|
||||||
if (!response.getApproved()) {
|
if (!Boolean.TRUE.equals(response.getApproved())) {
|
||||||
throw new ContentBlockedException(response.getReason(), response.getDetectedLabels());
|
throw new ContentBlockedException(response.getReason(), response.getDetectedLabels());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Boolean.TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildText(String title, String description) {
|
private String buildText(String title, String description) {
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
package com.krylov.refound.config;
|
|
||||||
|
|
||||||
import org.springframework.cache.annotation.EnableCaching;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
@EnableCaching
|
|
||||||
@Configuration
|
|
||||||
public class CacheConfig {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package com.krylov.refound.config;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
|
||||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
|
||||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class RedisConfig {
|
|
||||||
@Value("${cache.ttl}")
|
|
||||||
private long ttl;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
|
|
||||||
|
|
||||||
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
|
|
||||||
.entryTtl(Duration.ofSeconds(ttl))
|
|
||||||
.disableCachingNullValues();
|
|
||||||
|
|
||||||
return RedisCacheManager.builder(factory)
|
|
||||||
.cacheDefaults(config)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -44,7 +44,12 @@ public class PostModerationExecutorService {
|
|||||||
.map(this::toMultipartFile)
|
.map(this::toMultipartFile)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
contentModerationFacade.moderate(post.getTitle(), post.getDescription(), images);
|
Boolean approved = contentModerationFacade.moderate(post.getTitle(), post.getDescription(), images);
|
||||||
|
|
||||||
|
if (approved == null) {
|
||||||
|
log.warn("Пост {} не отмодерирован из-за недоступности AI, статус не меняем", post.getId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
postRepository.updateStatus(postId, PostStatus.ACTIVE);
|
postRepository.updateStatus(postId, PostStatus.ACTIVE);
|
||||||
log.info("Пост {} прошёл модерацию.", post.getId());
|
log.info("Пост {} прошёл модерацию.", post.getId());
|
||||||
|
|||||||
@@ -23,9 +23,6 @@ spring:
|
|||||||
host: localhost
|
host: localhost
|
||||||
port: 6379
|
port: 6379
|
||||||
|
|
||||||
cache:
|
|
||||||
type: redis
|
|
||||||
|
|
||||||
liquibase:
|
liquibase:
|
||||||
change-log: classpath:db/changelog/db.changelog-master.yaml
|
change-log: classpath:db/changelog/db.changelog-master.yaml
|
||||||
|
|
||||||
@@ -44,9 +41,6 @@ ai:
|
|||||||
delay: 500
|
delay: 500
|
||||||
multiplier: 2
|
multiplier: 2
|
||||||
|
|
||||||
cache:
|
|
||||||
ttl: 300
|
|
||||||
|
|
||||||
jwt:
|
jwt:
|
||||||
secret: ${JWT_SECRET:mySuperSecretKeyForJwtTokenGenerationThatIsLongEnough2024!ReFound}
|
secret: ${JWT_SECRET:mySuperSecretKeyForJwtTokenGenerationThatIsLongEnough2024!ReFound}
|
||||||
access-expiration: ${JWT_ACCESS_EXPIRATION:900000} # 15 минут
|
access-expiration: ${JWT_ACCESS_EXPIRATION:900000} # 15 минут
|
||||||
|
|||||||
Reference in New Issue
Block a user