NoticeServiceImpl.java
package org.darkroomlibrary.service.impl;
import jakarta.annotation.Resource;
import org.darkroomlibrary.mapper.NoticeMapper;
import org.darkroomlibrary.domain.model.Notice;
import org.darkroomlibrary.domain.type.FileReferenceType;
import org.darkroomlibrary.service.FileStorageService;
import org.darkroomlibrary.service.NoticeService;
import org.darkroomlibrary.utils.ContentSanitizer;
import org.darkroomlibrary.utils.IdListUtils;
import org.darkroomlibrary.web.dto.query.NoticePageQuery;
import org.darkroomlibrary.web.response.ApiResponse;
import org.darkroomlibrary.web.response.PageResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import java.time.LocalDateTime;
import java.util.List;
/**
* 公告的写入、安全清洗、文件引用和分页查询。
*/
@Service
public class NoticeServiceImpl implements NoticeService {
@Resource
private NoticeMapper noticeMapper;
@Resource
private FileStorageService fileStorageService;
@Override
@Transactional
public ApiResponse<Void> save(Notice notice) {
String validationError = sanitizeAndValidate(notice);
if (validationError != null) {
return ApiResponse.error(validationError);
}
notice.setCreateTime(LocalDateTime.now());
if (noticeMapper.insert(notice) != 1) {
return ApiResponse.error("公告发布失败,请重试");
}
if (!bindAssets(notice)) {
return invalidAssetResponse();
}
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 + "条公告");
}
if (noticeMapper.findByIdsForUpdate(normalizedIds).size() != normalizedIds.size()) {
return ApiResponse.error("部分公告不存在");
}
fileStorageService.releaseReferences(FileReferenceType.NOTICE_ASSET, normalizedIds);
if (noticeMapper.deleteByIds(normalizedIds) != normalizedIds.size()) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return ApiResponse.error("公告状态已变化,请刷新后重试");
}
return ApiResponse.success();
}
@Override
@Transactional
public ApiResponse<Void> update(Notice notice) {
String validationError = sanitizeAndValidate(notice);
if (validationError != null) {
return ApiResponse.error(validationError);
}
if (notice.getId() == null || noticeMapper.findByIdForUpdate(notice.getId()) == null) {
return ApiResponse.error("公告不存在");
}
if (noticeMapper.updateById(notice) == 0) {
return ApiResponse.error("公告状态已变化,请刷新后重试");
}
if (!bindAssets(notice)) {
return invalidAssetResponse();
}
return ApiResponse.success();
}
@Override
public ApiResponse<List<Notice>> query(NoticePageQuery query) {
List<Notice> notices = noticeMapper.findPage(query);
notices.forEach(this::sanitizeForResponse);
return PageResponse.success(notices, noticeMapper.countMatching(query));
}
private boolean bindAssets(Notice notice) {
return fileStorageService.bindFromHtml(
notice.getContent(),
FileReferenceType.NOTICE_ASSET,
notice.getId()
);
}
private ApiResponse<Void> invalidAssetResponse() {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return ApiResponse.error("公告中的文件无效或不属于当前用户");
}
private void sanitizeForResponse(Notice notice) {
notice.setName(ContentSanitizer.plainText(notice.getName()));
notice.setContent(ContentSanitizer.richText(notice.getContent()));
}
private String sanitizeAndValidate(Notice notice) {
if (notice == null) {
return "公告参数不能为空";
}
if (ContentSanitizer.exceedsLength(
notice.getName(), ContentSanitizer.NOTICE_TITLE_MAX_LENGTH)) {
return "公告标题不能超过100个字符";
}
String cleanName = ContentSanitizer.plainText(notice.getName());
if (cleanName == null || cleanName.isEmpty()) {
return "公告标题不能为空";
}
if (ContentSanitizer.exceedsLength(
notice.getContent(), ContentSanitizer.NOTICE_HTML_MAX_LENGTH)) {
return "公告内容不能超过20000个字符";
}
String cleanContent = ContentSanitizer.richText(notice.getContent());
if (!ContentSanitizer.hasVisibleRichText(cleanContent)) {
return "公告内容不能为空";
}
notice.setName(cleanName);
notice.setContent(cleanContent);
return null;
}
}