Files
happy-life-star/server/src/main/java/com/emotion/service/impl/SocialContentServiceImpl.java
T

254 lines
11 KiB
Java

package com.emotion.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.emotion.dto.request.social.SocialContentLinkImportRequest;
import com.emotion.dto.request.social.SocialContentManualImportRequest;
import com.emotion.dto.response.social.SocialContentItemResponse;
import com.emotion.entity.SocialContentItem;
import com.emotion.entity.SocialProfileInsight;
import com.emotion.entity.UserConsentLog;
import com.emotion.mapper.SocialContentItemMapper;
import com.emotion.mapper.SocialProfileInsightMapper;
import com.emotion.mapper.UserConsentLogMapper;
import com.emotion.service.SocialContentService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
@Service
public class SocialContentServiceImpl extends ServiceImpl<SocialContentItemMapper, SocialContentItem>
implements SocialContentService {
private static final Set<String> PLATFORMS = Set.of("xiaohongshu", "weibo", "wechat", "other");
private static final int MAX_CONTENT_LENGTH = 20000;
private static final long MAX_SCREENSHOT_SIZE = 5L * 1024L * 1024L;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Autowired
private UserConsentLogMapper userConsentLogMapper;
@Autowired
private SocialProfileInsightMapper socialProfileInsightMapper;
@Override
public SocialContentItemResponse manualImport(String userId, SocialContentManualImportRequest request) {
validateUser(userId);
String platform = normalizePlatform(request.getPlatform());
String content = normalizeContent(request.getContent());
SocialContentItem item = findDuplicate(userId, platform, content);
if (item == null) {
item = new SocialContentItem();
item.setUserId(userId);
item.setPlatform(platform);
item.setSourceType("manual_text");
item.setTitle(trimToNull(request.getTitle()));
item.setContent(content);
item.setImportStatus("parsed");
item.setApprovedForAi(Boolean.TRUE.equals(request.getApprovedForAi()) ? 1 : 0);
item.setContentHash(hashContent(content));
this.save(item);
} else if (Boolean.TRUE.equals(request.getApprovedForAi()) && !isApproved(item)) {
item.setApprovedForAi(1);
this.updateById(item);
}
logConsentIfApproved(userId, platform, item.getApprovedForAi());
return convertToResponse(item);
}
@Override
public SocialContentItemResponse linkImport(String userId, SocialContentLinkImportRequest request) {
validateUser(userId);
String platform = normalizePlatform(request.getPlatform());
if (!StringUtils.hasText(request.getSourceUrl())) {
throw new IllegalArgumentException("来源链接不能为空");
}
String content = StringUtils.hasText(request.getContent())
? normalizeContent(request.getContent())
: normalizeContent(request.getSourceUrl());
SocialContentItem item = findDuplicate(userId, platform, content);
if (item == null) {
item = new SocialContentItem();
item.setUserId(userId);
item.setPlatform(platform);
item.setSourceType("public_link");
item.setSourceUrl(request.getSourceUrl().trim());
item.setTitle(trimToNull(request.getTitle()));
item.setContent(content);
item.setImportStatus("parsed");
item.setApprovedForAi(Boolean.TRUE.equals(request.getApprovedForAi()) ? 1 : 0);
item.setContentHash(hashContent(content));
this.save(item);
} else if (Boolean.TRUE.equals(request.getApprovedForAi()) && !isApproved(item)) {
item.setApprovedForAi(1);
this.updateById(item);
}
logConsentIfApproved(userId, platform, item.getApprovedForAi());
return convertToResponse(item);
}
@Override
public SocialContentItemResponse screenshotImport(String userId, String platform, MultipartFile file) {
validateUser(userId);
normalizePlatform(platform);
if (file == null || file.isEmpty()) {
throw new IllegalArgumentException("截图文件不能为空");
}
if (file.getSize() > MAX_SCREENSHOT_SIZE) {
throw new IllegalArgumentException("截图不能超过5MB");
}
String name = file.getOriginalFilename() == null ? "" : file.getOriginalFilename().toLowerCase(Locale.ROOT);
if (!(name.endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".png") || name.endsWith(".webp"))) {
throw new IllegalArgumentException("仅支持jpg、png、webp截图");
}
throw new IllegalStateException("OCR暂未启用,请先使用粘贴文本导入");
}
@Override
public List<SocialContentItemResponse> listByUser(String userId) {
validateUser(userId);
LambdaQueryWrapper<SocialContentItem> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SocialContentItem::getUserId, userId)
.eq(SocialContentItem::getIsDeleted, 0)
.orderByDesc(SocialContentItem::getCreateTime);
return this.list(wrapper).stream().map(this::convertToResponse).collect(Collectors.toList());
}
@Override
public boolean deleteByUser(String userId, String id, Boolean keepConfirmedInsights) {
validateUser(userId);
SocialContentItem item = getOwned(userId, id);
if (item == null) {
return false;
}
item.setIsDeleted(1);
item.setDeletedAt(LocalDateTime.now());
this.updateById(item);
LambdaUpdateWrapper<SocialProfileInsight> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(SocialProfileInsight::getUserId, userId)
.eq(SocialProfileInsight::getSourceItemId, id)
.eq(SocialProfileInsight::getIsDeleted, 0);
if (Boolean.TRUE.equals(keepConfirmedInsights)) {
wrapper.ne(SocialProfileInsight::getStatus, "confirmed");
}
wrapper.set(SocialProfileInsight::getStatus, "deleted")
.set(SocialProfileInsight::getIsDeleted, 1)
.set(SocialProfileInsight::getDeletedAt, LocalDateTime.now());
socialProfileInsightMapper.update(null, wrapper);
return true;
}
@Override
public SocialContentItemResponse updateApproval(String userId, String id, Boolean approvedForAi) {
validateUser(userId);
SocialContentItem item = getOwned(userId, id);
if (item == null) {
return null;
}
item.setApprovedForAi(Boolean.TRUE.equals(approvedForAi) ? 1 : 0);
this.updateById(item);
logConsentIfApproved(userId, item.getPlatform(), item.getApprovedForAi());
return convertToResponse(item);
}
private SocialContentItem getOwned(String userId, String id) {
if (!StringUtils.hasText(id)) {
return null;
}
LambdaQueryWrapper<SocialContentItem> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SocialContentItem::getId, id)
.eq(SocialContentItem::getUserId, userId)
.eq(SocialContentItem::getIsDeleted, 0);
return this.getOne(wrapper, false);
}
private SocialContentItem findDuplicate(String userId, String platform, String content) {
LambdaQueryWrapper<SocialContentItem> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SocialContentItem::getUserId, userId)
.eq(SocialContentItem::getPlatform, platform)
.eq(SocialContentItem::getContentHash, hashContent(content))
.eq(SocialContentItem::getIsDeleted, 0);
return this.getOne(wrapper, false);
}
private void logConsentIfApproved(String userId, String platform, Integer approvedForAi) {
if (approvedForAi == null || approvedForAi != 1) {
return;
}
UserConsentLog log = new UserConsentLog();
log.setUserId(userId);
log.setPlatform(platform);
log.setConsentType("ai_profile_analysis");
log.setConsentVersion("v1");
log.setScope("imported_social_content");
log.setPurpose("用于生成可编辑的人生画像,并增强人生剧本生成");
log.setStatus("granted");
log.setGrantedAt(LocalDateTime.now());
userConsentLogMapper.insert(log);
}
private String normalizePlatform(String platform) {
String value = platform == null ? "" : platform.trim().toLowerCase(Locale.ROOT);
if (!PLATFORMS.contains(value)) {
throw new IllegalArgumentException("不支持的平台");
}
return value;
}
private String normalizeContent(String content) {
if (!StringUtils.hasText(content)) {
throw new IllegalArgumentException("导入内容不能为空");
}
String normalized = content.replaceAll("\\s+", " ").trim();
if (normalized.length() > MAX_CONTENT_LENGTH) {
throw new IllegalArgumentException("导入内容不能超过20000个字符");
}
return normalized;
}
private String hashContent(String content) {
return DigestUtils.md5DigestAsHex(content.getBytes(StandardCharsets.UTF_8));
}
private boolean isApproved(SocialContentItem item) {
return item.getApprovedForAi() != null && item.getApprovedForAi() == 1;
}
private String trimToNull(String value) {
return StringUtils.hasText(value) ? value.trim() : null;
}
private void validateUser(String userId) {
if (!StringUtils.hasText(userId)) {
throw new IllegalArgumentException("用户未登录");
}
}
private SocialContentItemResponse convertToResponse(SocialContentItem item) {
SocialContentItemResponse response = new SocialContentItemResponse();
BeanUtils.copyProperties(item, response);
response.setApprovedForAi(isApproved(item));
response.setSourceDeleted(item.getIsDeleted() != null && item.getIsDeleted() == 1);
if (item.getCreateTime() != null) {
response.setCreateTime(item.getCreateTime().format(DATE_TIME_FORMATTER));
}
if (item.getUpdateTime() != null) {
response.setUpdateTime(item.getUpdateTime().format(DATE_TIME_FORMATTER));
}
return response;
}
}