Files
YschcmsService/src/main/java/com/yunhai/cms/service/impl/BannerService.java
T

142 lines
6.6 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.yunhai.cms.service.impl;
import com.yunhai.cms.entity.CmsBanner;
import com.yunhai.cms.entity.CmsSite;
import com.yunhai.cms.enums.LinkTypeEnum;
import com.yunhai.cms.event.SiteContentChangedEvent;
import com.yunhai.cms.model.dto.BannerSaveRequest;
import com.yunhai.cms.model.dto.DeleteRequest;
import com.yunhai.cms.model.vo.BannerVo;
import com.yunhai.cms.repository.CmsArticleRepository;
import com.yunhai.cms.repository.CmsBannerRepository;
import com.yunhai.cms.repository.CmsCategoryRepository;
import com.yunhai.platform.apistd.BizException;
import com.yunhai.platform.utils.exception.ErrorCode;
import com.yunhai.platform.utils.model.BoaUser;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
/**
* 轮播/推荐位管理(方案 §9.1 轮播管理)。保存时校验 link_type/link_value 匹配。
*/
@Service
@RequiredArgsConstructor
public class BannerService {
private final CmsBannerRepository bannerRepository;
private final CmsArticleRepository articleRepository;
private final CmsCategoryRepository categoryRepository;
private final SiteService siteService;
private final ApplicationEventPublisher eventPublisher;
private final AttachmentService attachmentService;
public List<BannerVo> list(BoaUser user, Integer siteId, String position) {
siteService.requireManageable(user, siteId);
List<CmsBanner> banners = StringUtils.hasText(position)
? bannerRepository.findBySiteIdAndPositionOrderBySortNoAscIdAsc(siteId, position)
: bannerRepository.findBySiteIdOrderByPositionAscSortNoAscIdAsc(siteId);
List<BannerVo> result = new ArrayList<>();
for (CmsBanner b : banners) {
result.add(BannerVo.of(b));
}
return result;
}
@Transactional
public BannerVo save(BoaUser user, BannerSaveRequest req) {
CmsSite site = siteService.requireManageable(user, req.getSiteId());
LinkTypeEnum linkType = LinkTypeEnum.ofCode(req.getLinkType());
if (linkType == null) {
throw new BizException(ErrorCode.PARAM_ERROR, "跳转类型非法:" + req.getLinkType() + "(仅 1=文章 2=栏目 3=外链)");
}
validateLink(site.getId(), linkType, req.getLinkValue());
CmsBanner banner;
if (req.getId() == null) {
banner = new CmsBanner();
banner.setSiteId(site.getId());
} else {
banner = bannerRepository.findById(req.getId())
.orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "轮播不存在"));
if (!banner.getSiteId().equals(site.getId())) {
throw new BizException(ErrorCode.FORBIDDEN, "无权修改该轮播");
}
}
banner.setPosition(StringUtils.hasText(req.getPosition()) ? req.getPosition() : "home");
banner.setTitle(req.getTitle());
banner.setImageUrl(req.getImageUrl());
banner.setLinkType(linkType.getCode());
banner.setLinkValue(req.getLinkValue());
banner.setSortNo(req.getSortNo() == null ? 0 : req.getSortNo());
banner.setStartTime(req.getStartTime());
banner.setEndTime(req.getEndTime());
if (banner.getStartTime() != null && banner.getEndTime() != null
&& banner.getEndTime().isBefore(banner.getStartTime())) {
throw new BizException(ErrorCode.PARAM_ERROR, "下线时间不能早于上线时间");
}
banner.setStatus(req.getStatus() == null ? 1 : req.getStatus());
// docs/20 D-3/D-6:topic/event 位扩展字段透传(其余位一并落库,渲染端按位消费)
banner.setDescription(req.getDescription());
banner.setCtaText(req.getCtaText());
banner.setCtaUrl(req.getCtaUrl());
banner.setEventDate(req.getEventDate());
banner.setLocation(req.getLocation());
banner = bannerRepository.save(banner);
// B2 附件引用:先解引用再回写新图(编辑换图时旧图回未使用);幂等
attachmentService.release("banner", banner.getId());
attachmentService.markUsed(site.getId(), req.getImageUrl(), "banner", banner.getId());
eventPublisher.publishEvent(new SiteContentChangedEvent(site.getId(), "banner"));
return BannerVo.of(banner);
}
@Transactional
public void delete(BoaUser user, DeleteRequest req) {
CmsSite site = siteService.requireManageable(user, req.getSiteId());
CmsBanner banner = bannerRepository.findById(req.getId())
.orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "轮播不存在"));
if (!banner.getSiteId().equals(site.getId())) {
throw new BizException(ErrorCode.FORBIDDEN, "无权删除该轮播");
}
bannerRepository.delete(banner);
attachmentService.release("banner", banner.getId());
eventPublisher.publishEvent(new SiteContentChangedEvent(site.getId(), "banner"));
}
/**
* link_type/link_value 匹配校验:1=文章 2=栏目(须存在且属于本站,禁止跨站引用)
* 3=外链(必须 http/https 开头)。
*/
private void validateLink(Integer siteId, LinkTypeEnum linkType, String linkValue) {
if (linkType == LinkTypeEnum.EXTERNAL) {
if (!StringUtils.hasText(linkValue)
|| !(linkValue.startsWith("http://") || linkValue.startsWith("https://"))) {
throw new BizException(ErrorCode.PARAM_ERROR, "外链地址必须以 http(s):// 开头");
}
return;
}
if (!StringUtils.hasText(linkValue)) {
throw new BizException(ErrorCode.PARAM_ERROR, "跳转目标不能为空");
}
Integer targetId;
try {
targetId = Integer.valueOf(linkValue.trim());
} catch (NumberFormatException e) {
throw new BizException(ErrorCode.PARAM_ERROR, "跳转目标必须是数字 ID");
}
if (linkType == LinkTypeEnum.ARTICLE) {
articleRepository.findByIdAndSiteId(targetId, siteId)
.orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "跳转文章不存在"));
} else if (linkType == LinkTypeEnum.CATEGORY) {
categoryRepository.findById(targetId)
.filter(c -> c.getSiteId().equals(siteId))
.orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "跳转栏目不存在"));
}
}
}