refactor: 重命名 backend-single 目录为 server
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user