完善业务闭环,实现对轮播图的管理,优化数据库表文件

This commit is contained in:
2026-07-18 16:21:40 +08:00
parent feaf014b4a
commit 97a5eff678
78 changed files with 3210 additions and 100 deletions
@@ -0,0 +1,97 @@
package cn.novalon.gym.manage.notify.core.domain;
import java.time.LocalDateTime;
public class Banner {
private Long id;
private String imageUrl;
private String title;
private String subtitle;
private Integer sortOrder;
private String isActive;
private String linkUrl;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getSubtitle() {
return subtitle;
}
public void setSubtitle(String subtitle) {
this.subtitle = subtitle;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public String getIsActive() {
return isActive;
}
public void setIsActive(String isActive) {
this.isActive = isActive;
}
public String getLinkUrl() {
return linkUrl;
}
public void setLinkUrl(String linkUrl) {
this.linkUrl = linkUrl;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public LocalDateTime getDeletedAt() {
return deletedAt;
}
public void setDeletedAt(LocalDateTime deletedAt) {
this.deletedAt = deletedAt;
}
}
@@ -0,0 +1,18 @@
package cn.novalon.gym.manage.notify.core.repository;
import cn.novalon.gym.manage.notify.core.domain.Banner;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface IBannerRepository {
Flux<Banner> findByDeletedAtIsNull();
Flux<Banner> findActiveBanners();
Mono<Banner> findById(Long id);
Mono<Banner> save(Banner banner);
Mono<Void> deleteByIdAndDeletedAtIsNull(Long id);
}
@@ -0,0 +1,20 @@
package cn.novalon.gym.manage.notify.core.service;
import cn.novalon.gym.manage.notify.core.domain.Banner;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface IBannerService {
Flux<Banner> getAllBanners();
Flux<Banner> getActiveBanners();
Mono<Banner> getBannerById(Long id);
Mono<Banner> createBanner(Banner banner);
Mono<Banner> updateBanner(Long id, Banner banner);
Mono<Void> deleteBanner(Long id);
}
@@ -0,0 +1,80 @@
package cn.novalon.gym.manage.notify.core.service.impl;
import cn.novalon.gym.manage.notify.core.domain.Banner;
import cn.novalon.gym.manage.notify.core.repository.IBannerRepository;
import cn.novalon.gym.manage.notify.core.service.IBannerService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
@Service
public class BannerServiceImpl implements IBannerService {
private final IBannerRepository bannerRepository;
public BannerServiceImpl(IBannerRepository bannerRepository) {
this.bannerRepository = bannerRepository;
}
@Override
public Flux<Banner> getAllBanners() {
return bannerRepository.findByDeletedAtIsNull();
}
@Override
public Flux<Banner> getActiveBanners() {
return bannerRepository.findActiveBanners();
}
@Override
public Mono<Banner> getBannerById(Long id) {
return bannerRepository.findById(id)
.filter(banner -> banner.getDeletedAt() == null);
}
@Override
public Mono<Banner> createBanner(Banner banner) {
banner.setCreatedAt(LocalDateTime.now());
return bannerRepository.save(banner);
}
@Override
public Mono<Banner> updateBanner(Long id, Banner banner) {
return bannerRepository.findById(id)
.flatMap(existing -> {
if (banner.getImageUrl() != null) {
existing.setImageUrl(banner.getImageUrl());
}
if (banner.getTitle() != null) {
existing.setTitle(banner.getTitle());
}
if (banner.getSubtitle() != null) {
existing.setSubtitle(banner.getSubtitle());
}
if (banner.getSortOrder() != null) {
existing.setSortOrder(banner.getSortOrder());
}
if (banner.getIsActive() != null) {
existing.setIsActive(banner.getIsActive());
}
if (banner.getLinkUrl() != null) {
existing.setLinkUrl(banner.getLinkUrl());
}
existing.setUpdatedAt(LocalDateTime.now());
return bannerRepository.save(existing);
});
}
@Override
public Mono<Void> deleteBanner(Long id) {
return bannerRepository.findById(id)
.filter(banner -> banner.getDeletedAt() == null)
.flatMap(banner -> {
banner.setDeletedAt(LocalDateTime.now());
return bannerRepository.save(banner);
})
.then();
}
}
@@ -0,0 +1,120 @@
package cn.novalon.gym.manage.notify.handler;
import cn.novalon.gym.manage.notify.core.domain.Banner;
import cn.novalon.gym.manage.notify.core.service.IBannerService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.util.Map;
@Component
@Tag(name = "轮播图管理", description = "首页轮播图相关操作")
public class BannerHandler {
private final IBannerService bannerService;
public BannerHandler(IBannerService bannerService) {
this.bannerService = bannerService;
}
@Operation(summary = "获取所有轮播图", description = "获取系统中所有轮播图列表(按排序升序)")
public Mono<ServerResponse> getAllBanners(ServerRequest request) {
Flux<Banner> banners = bannerService.getAllBanners();
return ServerResponse.ok().body(banners, Banner.class);
}
@Operation(summary = "获取启用的轮播图", description = "获取所有启用状态的轮播图(按排序升序),供外部页面调用")
public Mono<ServerResponse> getActiveBanners(ServerRequest request) {
Flux<Banner> banners = bannerService.getActiveBanners();
return ServerResponse.ok().body(banners, Banner.class);
}
@Operation(summary = "根据ID获取轮播图", description = "根据轮播图ID获取详细信息")
public Mono<ServerResponse> getBannerById(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return bannerService.getBannerById(id)
.flatMap(banner -> ServerResponse.ok().bodyValue(banner))
.switchIfEmpty(ServerResponse.notFound().build());
}
@Operation(summary = "创建轮播图", description = "创建新轮播图")
public Mono<ServerResponse> createBanner(ServerRequest request) {
return request.bodyToMono(Map.class)
.map(this::mapToBanner)
.filter(banner -> banner.getImageUrl() != null && !banner.getImageUrl().trim().isEmpty())
.switchIfEmpty(Mono.error(new IllegalArgumentException("轮播图图片地址不能为空")))
.filter(banner -> banner.getTitle() != null && !banner.getTitle().trim().isEmpty())
.switchIfEmpty(Mono.error(new IllegalArgumentException("轮播图标题不能为空")))
.flatMap(banner -> {
if (banner.getSortOrder() == null) {
banner.setSortOrder(0);
}
if (banner.getIsActive() == null) {
banner.setIsActive("1");
}
return bannerService.createBanner(banner);
})
.flatMap(banner -> ServerResponse.ok().bodyValue(banner))
.onErrorResume(IllegalArgumentException.class, ex ->
ServerResponse.badRequest().bodyValue(Map.of(
"code", 400,
"message", ex.getMessage(),
"timestamp", LocalDateTime.now()
))
);
}
@Operation(summary = "更新轮播图", description = "更新轮播图信息")
public Mono<ServerResponse> updateBanner(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return request.bodyToMono(Map.class)
.map(this::mapToBanner)
.flatMap(banner -> bannerService.updateBanner(id, banner))
.flatMap(banner -> ServerResponse.ok().bodyValue(banner))
.switchIfEmpty(ServerResponse.notFound().build());
}
@Operation(summary = "删除轮播图", description = "删除指定轮播图(软删除)")
public Mono<ServerResponse> deleteBanner(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return bannerService.getBannerById(id)
.filter(banner -> banner.getDeletedAt() == null)
.flatMap(banner -> bannerService.deleteBanner(id)
.then(ServerResponse.noContent().build()))
.switchIfEmpty(ServerResponse.notFound().build());
}
/**
* 将前端传来的 Map 转换为 Banner 领域对象
* 兼容 isActive 以 boolean 或 string 形式传入
*/
private Banner mapToBanner(Map<String, Object> body) {
Banner banner = new Banner();
banner.setImageUrl((String) body.get("imageUrl"));
banner.setTitle((String) body.get("title"));
banner.setSubtitle((String) body.get("subtitle"));
banner.setLinkUrl((String) body.get("linkUrl"));
// sortOrder 处理
Object sortOrderObj = body.get("sortOrder");
if (sortOrderObj instanceof Number) {
banner.setSortOrder(((Number) sortOrderObj).intValue());
}
// isActive 兼容 boolean 和 string
Object isActiveObj = body.get("isActive");
if (isActiveObj instanceof Boolean) {
banner.setIsActive((Boolean) isActiveObj ? "1" : "0");
} else if (isActiveObj instanceof String) {
banner.setIsActive((String) isActiveObj);
}
return banner;
}
}