MessageBoardServiceImpl.java

package org.darkroomlibrary.service.impl;

import org.darkroomlibrary.context.CurrentUserContext;
import org.darkroomlibrary.mapper.MessageBoardMapper;
import org.darkroomlibrary.web.response.ApiResponse;
import org.darkroomlibrary.web.response.PageResponse;
import org.darkroomlibrary.web.dto.query.MessageBoardPageQuery;
import org.darkroomlibrary.domain.type.FileReferenceType;
import org.darkroomlibrary.domain.model.MessageBoard;
import org.darkroomlibrary.web.view.MessageBoardView;
import org.darkroomlibrary.service.ContentPostingPolicy;
import org.darkroomlibrary.service.MessageBoardService;
import org.darkroomlibrary.service.FileStorageService;
import org.darkroomlibrary.utils.ContentSanitizer;
import org.darkroomlibrary.utils.IdListUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

/**
 * 留言板服务实现
 */
@Service
public class MessageBoardServiceImpl implements MessageBoardService {

    @Resource
    private MessageBoardMapper messageBoardMapper;

    @Resource
    private FileStorageService fileStorageService;

    @Resource
    private ContentPostingPolicy contentPostingPolicy;

    @Override
    @Transactional
    public ApiResponse<Void> save(MessageBoard messageBoard) {
        if (messageBoard == null) {
            return ApiResponse.error("留言参数不能为空");
        }
        String postingError = contentPostingPolicy.currentUserRejectionReason();
        if (postingError != null) {
            return ApiResponse.error(postingError);
        }
        String content = messageBoard.getContent();
        if (ContentSanitizer.exceedsLength(content, ContentSanitizer.MESSAGE_MAX_LENGTH)) {
            return ApiResponse.error("留言内容不能超过1000个字符");
        }
        String cleanContent = ContentSanitizer.plainText(content);
        boolean hasContent = cleanContent != null && !cleanContent.isEmpty();
        boolean hasAttachment = messageBoard.getAttachmentUrl() != null
                && !messageBoard.getAttachmentUrl().trim().isEmpty();
        if (!hasContent && !hasAttachment) {
            return ApiResponse.error("留言内容和附件不能同时为空");
        }
        if (hasAttachment) {
            if (!ContentSanitizer.isSafeMessageAttachment(
                    messageBoard.getAttachmentUrl(), messageBoard.getAttachmentType())) {
                return ApiResponse.error("附件地址或类型不合法");
            }
            if (ContentSanitizer.exceedsLength(
                    messageBoard.getAttachmentName(), ContentSanitizer.ATTACHMENT_NAME_MAX_LENGTH)) {
                return ApiResponse.error("附件名称不能超过255个字符");
            }
            String cleanAttachmentName = ContentSanitizer.plainText(messageBoard.getAttachmentName());
            if (cleanAttachmentName == null || cleanAttachmentName.isEmpty()) {
                return ApiResponse.error("附件名称不能为空");
            }
            messageBoard.setAttachmentUrl(messageBoard.getAttachmentUrl().trim());
            messageBoard.setAttachmentName(cleanAttachmentName);
            messageBoard.setAttachmentType(messageBoard.getAttachmentType().trim().toLowerCase());
        } else {
            messageBoard.setAttachmentUrl(null);
            messageBoard.setAttachmentName(null);
            messageBoard.setAttachmentType(null);
        }
        Integer userId = CurrentUserContext.userId();
        messageBoard.setUserId(userId);
        messageBoard.setContent(hasContent ? cleanContent : "");
        messageBoard.setReply(null);
        messageBoard.setCreateTime(LocalDateTime.now());
        if (messageBoardMapper.insert(messageBoard) != 1) {
            return ApiResponse.error("留言失败,请重试");
        }
        if (hasAttachment) {
            if (!fileStorageService.bindSingle(
                    messageBoard.getAttachmentUrl(), FileReferenceType.MESSAGE_ATTACHMENT, messageBoard.getId())) {
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return ApiResponse.error("附件文件无效或不属于当前用户");
            }
            String downloadUrl = fileStorageService.toDownloadUrl(messageBoard.getAttachmentUrl());
            if (messageBoardMapper.update(MessageBoard.builder()
                    .id(messageBoard.getId())
                    .attachmentUrl(downloadUrl)
                    .build()) == 0) {
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return ApiResponse.error("留言状态已变化,请重试");
            }
            messageBoard.setAttachmentUrl(downloadUrl);
        }
        return ApiResponse.success("留言成功");
    }

    @Override
    @Transactional
    public ApiResponse<Void> batchDelete(List<Integer> ids) {
        List<Integer> normalizedIds = IdListUtils.normalize(ids);
        if (normalizedIds.isEmpty()) {
            return ApiResponse.error("请选择要删除的留言");
        }
        if (IdListUtils.exceedsBatchLimit(normalizedIds)) {
            return ApiResponse.error("单次最多删除" + IdListUtils.MAX_BATCH_SIZE + "条留言");
        }
        List<MessageBoard> messages = messageBoardMapper.findByIdsForUpdate(normalizedIds);
        if (messages.size() != normalizedIds.size()) {
            return ApiResponse.error("部分留言不存在");
        }
        // 非管理员只能删除自己的留言
        if (!CurrentUserContext.isAdministrator()) {
            Integer currentUserId = CurrentUserContext.userId();
            for (MessageBoard message : messages) {
                if (!Objects.equals(message.getUserId(), currentUserId)) {
                    return ApiResponse.error("只能删除自己的留言");
                }
            }
        }
        fileStorageService.releaseReferences(FileReferenceType.MESSAGE_ATTACHMENT, normalizedIds);
        if (messageBoardMapper.batchDelete(normalizedIds) != normalizedIds.size()) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return ApiResponse.error("留言状态已变化,请刷新后重试");
        }
        return ApiResponse.success("删除留言成功");
    }

    @Override
    public ApiResponse<List<MessageBoardView>> query(MessageBoardPageQuery dto) {
        List<MessageBoardView> list = messageBoardMapper.query(dto);
        for (MessageBoardView item : list) {
            item.setContent(ContentSanitizer.plainText(item.getContent()));
            item.setReply(ContentSanitizer.plainText(item.getReply()));
            item.setAttachmentName(ContentSanitizer.plainText(item.getAttachmentName()));
            item.setAttachmentUrl(fileStorageService.toDownloadUrl(item.getAttachmentUrl()));
            if (item.getAttachmentUrl() != null
                    && !ContentSanitizer.isSafeMessageAttachment(
                    item.getAttachmentUrl(), item.getAttachmentType())) {
                item.setAttachmentUrl(null);
                item.setAttachmentName(null);
                item.setAttachmentType(null);
            }
        }
        Integer total = messageBoardMapper.queryCount(dto);
        return PageResponse.success(list, total);
    }

    @Override
    @Transactional
    public ApiResponse<Void> reply(Integer id, String reply) {
        MessageBoard msg = id == null ? null : messageBoardMapper.findByIdForUpdate(id);
        if (msg == null) {
            return ApiResponse.error("留言不存在");
        }
        if (ContentSanitizer.exceedsLength(reply, ContentSanitizer.MESSAGE_REPLY_MAX_LENGTH)) {
            return ApiResponse.error("回复内容不能超过1000个字符");
        }
        String cleanReply = ContentSanitizer.plainText(reply);
        if (cleanReply == null || cleanReply.isEmpty()) {
            return ApiResponse.error("回复内容不能为空");
        }
        if (messageBoardMapper.update(MessageBoard.builder().id(id).reply(cleanReply).build()) == 0) {
            return ApiResponse.error("留言状态已变化,请刷新后重试");
        }
        return ApiResponse.success("回复成功");
    }
}