diff --git a/src/main/java/com/yunhai/userhome/controller/api/SchSettingsController.java b/src/main/java/com/yunhai/userhome/controller/api/SchSettingsController.java index ec434a1..7ff6f50 100644 --- a/src/main/java/com/yunhai/userhome/controller/api/SchSettingsController.java +++ b/src/main/java/com/yunhai/userhome/controller/api/SchSettingsController.java @@ -181,6 +181,38 @@ public class SchSettingsController { schSettingsService.save(schSettings); } + // ===== 默认壁纸(平台/学校两级,壁纸三级默认轮 2026-10-09):与 LOGO 文字同模式 ===== + + /** 默认壁纸读(已登录可读):{wallpaperUrl: string|null, schId=实际命中层}。本校空 → 平台层 → null。 */ + @GetMapping("schWallpaper") + public Map schWallpaper(BoaUser authUserWithDept) { + ISchSettingsService.LayerValue hit = + schSettingsService.readWallpaperWithFallback(schSettingsService.resolveThemeSchId(authUserWithDept, null)); + Map out = new HashMap<>(); + out.put("wallpaperUrl", hit.getValue()); + out.put("schId", hit.getHitSchId()); + return out; + } + + /** 保存默认壁纸(仅学校管理员/超管):trim 后落本级(超管=平台层 0,校管=本校);空串存 null(=清空)。 + * URL 为管理池返回的绝对地址原样存(与 user_settings.wallpaper_url 同口径),≤255。 */ + @PostMapping("saveSchWallpaper") + public void saveSchWallpaper(BoaUser authUserWithDept, + @RequestParam("wallpaperUrl") String wallpaperUrl) { + requireThemeAdmin(authUserWithDept); + String url = wallpaperUrl == null ? "" : wallpaperUrl.trim(); + if (url.length() > 255) { + throw new DException("壁纸地址不能超过 255 个字符"); + } + Integer targetSchId = schSettingsService.resolveThemeSchId(authUserWithDept, null); + SchSettings schSettings = schSettingsService.findBySchIdRaw(targetSchId).orElseGet(SchSettings::new); + schSettings.setCDate(new Date()); + schSettings.setSchId(targetSchId); + schSettings.setCId(authUserWithDept.getId()); + schSettings.setWallpaperUrl(url.isEmpty() ? null : url); + schSettingsService.save(schSettings); + } + @PostMapping("saveSchTheme") public void saveSchTheme(BoaUser authUserWithDept, String themeName, @RequestParam(name = "schId", defaultValue = "0") Integer schId) { if (schId == null || schId == 0) { diff --git a/src/main/java/com/yunhai/userhome/controller/api/UserSettingsController.java b/src/main/java/com/yunhai/userhome/controller/api/UserSettingsController.java index 74ea814..1a69959 100644 --- a/src/main/java/com/yunhai/userhome/controller/api/UserSettingsController.java +++ b/src/main/java/com/yunhai/userhome/controller/api/UserSettingsController.java @@ -16,6 +16,7 @@ import com.yunhai.userhome.enums.LightAppGroup; import com.yunhai.userhome.enums.LinkOpenType; import com.yunhai.userhome.model.AppIconEntryVo; import com.yunhai.userhome.service.AppIconKeyService; +import com.yunhai.userhome.service.ISchSettingsService; import com.yunhai.userhome.service.IUserCurrentRoleService; import com.yunhai.userhome.service.IUserSettingsService; import com.yunhai.userhome.service.UsedAppService; @@ -52,6 +53,9 @@ public class UserSettingsController { @Autowired private IUserSettingsService userSettingsService; + @Autowired + private ISchSettingsService schSettingsService; + @Autowired private IUserCurrentRoleService userCurrentRoleService; @@ -305,13 +309,20 @@ public class UserSettingsController { } /** - * 兜底壁纸(用户未设置时):本地学校壁纸第一张 → 超管全局壁纸第一张。 + * 兜底壁纸(用户未自选时,壁纸三级默认轮 2026-10-09):本校默认(sch_settings.wallpaper_url,管理员选定) + * → 平台默认(schId=0 行)→ 本地学校壁纸池最新一张 → 平台池最新一张(历史兜底,两级皆未配置时保持原体验)。 *

学校壁纸完全本地管理(wallpaper 表 type=1),不再依赖跨仓 schConfig。

*/ private String resolveFallbackWallpaper(BoaUser authUserWithDept, Integer schId) { + // 1/2. 管理员选定的默认壁纸(本校 → 平台 级联,读侧 LayerValue) + ISchSettingsService.LayerValue wallHit = schSettingsService.readWallpaperWithFallback(schId); + if (wallHit.getValue() != null) { + return wallHit.getValue(); + } + // 3/4. 历史兜底:壁纸池最新(校 type=1 → 平台 type=0) List wallList = userSettingsService.findWallList(authUserWithDept.getId()); if (schId != null) { - // 1. 本地学校壁纸第一张(type=1,取最新) + // 3. 本地学校壁纸第一张(type=1,取最新) Optional schWall = wallList.stream() .filter(w -> w.getType() != null && w.getType() == 1 && w.getSchId() != null && w.getSchId().equals(schId)) @@ -320,7 +331,7 @@ public class UserSettingsController { return FILE_WEB_PATH + schWall.get().getWallpaperUrl().replace("/upload", ""); } } - // 2. 超管全局壁纸第一张(type=0,取最新) + // 4. 超管全局壁纸第一张(type=0,取最新) Optional sysWall = wallList.stream() .filter(w -> w.getType() != null && w.getType() == 0) .max(Comparator.comparing(Wallpaper::getId)); diff --git a/src/main/java/com/yunhai/userhome/entity/SchSettings.java b/src/main/java/com/yunhai/userhome/entity/SchSettings.java index c97ec56..d33fb3a 100644 --- a/src/main/java/com/yunhai/userhome/entity/SchSettings.java +++ b/src/main/java/com/yunhai/userhome/entity/SchSettings.java @@ -41,6 +41,11 @@ public class SchSettings { @Column(name = "logo_text") private String logoText; + /** 默认壁纸(壁纸三级默认轮 2026-10-09;绝对 URL,管理员从本级壁纸池选定;NULL=未配置回落上级。 + * 个人未自选时生效链:本校默认 → 平台默认(schId=0 行)→ 壁纸池最新一张(历史兜底) */ + @Column(name = "wallpaper_url") + private String wallpaperUrl; + @Column(name = "home_img_url") private String homeImgUrl; diff --git a/src/main/java/com/yunhai/userhome/service/ISchSettingsService.java b/src/main/java/com/yunhai/userhome/service/ISchSettingsService.java index 5bffa29..a1bd9f2 100644 --- a/src/main/java/com/yunhai/userhome/service/ISchSettingsService.java +++ b/src/main/java/com/yunhai/userhome/service/ISchSettingsService.java @@ -41,6 +41,9 @@ public interface ISchSettingsService { /** LOGO 文字读侧级联(LOGO 文字轮 2026-10-09):本校空 → 平台层 → null。 */ LayerValue readLogoTextWithFallback(Integer schId); + /** 默认壁纸读侧级联(壁纸三级默认轮 2026-10-09):本校空 → 平台层 → null。 */ + LayerValue readWallpaperWithFallback(Integer schId); + /** 级联读命中结果:{@code value}=命中的档位值(两级都无则 null),{@code hitSchId}=实际命中层的 schId * (回落发生时是 0,不是解析出的本校 schId)。设置中心读端点的响应体 schId 即此语义—— * 前端据此区分「本校已配置」与「本校未配置、正在回落平台默认」。 */ diff --git a/src/main/java/com/yunhai/userhome/service/impl/SchSettingsServiceImpl.java b/src/main/java/com/yunhai/userhome/service/impl/SchSettingsServiceImpl.java index 34e60b5..bb96ae4 100644 --- a/src/main/java/com/yunhai/userhome/service/impl/SchSettingsServiceImpl.java +++ b/src/main/java/com/yunhai/userhome/service/impl/SchSettingsServiceImpl.java @@ -115,6 +115,25 @@ public class SchSettingsServiceImpl implements ISchSettingsService { .orElse(null); } + @Override + public ISchSettingsService.LayerValue readWallpaperWithFallback(Integer schId) { + String url = readWallpaperAt(schId); + if (url != null || schId == null || schId == ISchSettingsService.PLATFORM_SCH_ID) { + return new ISchSettingsService.LayerValue(url, schId); + } + return new ISchSettingsService.LayerValue(readWallpaperAt(ISchSettingsService.PLATFORM_SCH_ID), + ISchSettingsService.PLATFORM_SCH_ID); + } + + /** 原样读某层默认壁纸(查无行/空白 → null;trim 后存储口径,URL 为管理员从池中选定的原值) */ + private String readWallpaperAt(Integer schId) { + return schSettingsRepository.findFirstBySchId(schId) + .map(SchSettings::getWallpaperUrl) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .orElse(null); + } + /** 原样读某层主题档并解码(查无行/旧格式/脏值 → null) */ private String decodeTheme(Integer schId) { return schSettingsRepository.findFirstBySchId(schId) diff --git a/src/test/java/com/yunhai/userhome/service/SchSettingsServiceTest.java b/src/test/java/com/yunhai/userhome/service/SchSettingsServiceTest.java new file mode 100644 index 0000000..268fb8b --- /dev/null +++ b/src/test/java/com/yunhai/userhome/service/SchSettingsServiceTest.java @@ -0,0 +1,93 @@ +package com.yunhai.userhome.service; + +import com.yunhai.userhome.entity.SchSettings; +import com.yunhai.userhome.repository.SchSettingsRepository; +import com.yunhai.userhome.service.impl.SchSettingsServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.when; + +/** + * sch_settings 读侧级联单测(LOGO 文字轮 2026-10-09 / 壁纸三级默认轮 2026-10-09): + * 本校未配置 → 平台层(schId=0)回落,LayerValue.hitSchId 标记实际命中层;两级皆空 → null。 + */ +class SchSettingsServiceTest { + + private SchSettingsRepository schSettingsRepository; + private SchSettingsServiceImpl service; + + @BeforeEach + void setUp() { + schSettingsRepository = Mockito.mock(SchSettingsRepository.class); + service = new SchSettingsServiceImpl(); + ReflectionTestUtils.setField(service, "schSettingsRepository", schSettingsRepository); + } + + private SchSettings row(Integer schId, String logoText, String wallpaperUrl) { + SchSettings s = new SchSettings(); + s.setSchId(schId); + s.setLogoText(logoText); + s.setWallpaperUrl(wallpaperUrl); + return s; + } + + @Test + @DisplayName("默认壁纸:本校已配置 → 命中本校层(hitSchId=本校)") + void wallpaperSchoolHit() { + when(schSettingsRepository.findFirstBySchId(3)) + .thenReturn(Optional.of(row(3, null, "http://base.test/upl/sch.png"))); + ISchSettingsService.LayerValue hit = service.readWallpaperWithFallback(3); + assertEquals("http://base.test/upl/sch.png", hit.getValue()); + assertEquals(3, hit.getHitSchId()); + } + + @Test + @DisplayName("默认壁纸:本校未配置 → 回落平台层(hitSchId=0)") + void wallpaperFallsToPlatform() { + when(schSettingsRepository.findFirstBySchId(3)).thenReturn(Optional.empty()); + when(schSettingsRepository.findFirstBySchId(0)) + .thenReturn(Optional.of(row(0, null, "http://base.test/upl/platform.png"))); + ISchSettingsService.LayerValue hit = service.readWallpaperWithFallback(3); + assertEquals("http://base.test/upl/platform.png", hit.getValue()); + assertEquals(0, hit.getHitSchId()); + } + + @Test + @DisplayName("默认壁纸:本校空白 → 回落平台层,平台也未配置 → value=null、hitSchId=0(命中层口径)") + void wallpaperBothEmpty() { + when(schSettingsRepository.findFirstBySchId(3)).thenReturn(Optional.of(row(3, null, " "))); + when(schSettingsRepository.findFirstBySchId(0)).thenReturn(Optional.empty()); + ISchSettingsService.LayerValue hit = service.readWallpaperWithFallback(3); + assertNull(hit.getValue()); + assertEquals(0, hit.getHitSchId()); + } + + @Test + @DisplayName("默认壁纸:schId 已是平台层(0)→ 不二次自查,直接返回该层值/null") + void wallpaperPlatformLayerNoDoubleFallback() { + when(schSettingsRepository.findFirstBySchId(0)).thenReturn(Optional.empty()); + ISchSettingsService.LayerValue hit = service.readWallpaperWithFallback(0); + assertNull(hit.getValue()); + assertEquals(0, hit.getHitSchId()); + Mockito.verify(schSettingsRepository, Mockito.times(1)).findFirstBySchId(Mockito.any()); + } + + @Test + @DisplayName("LOGO 文字:本校空 → 平台回落(级联读同构回归)") + void logoTextFallsToPlatform() { + when(schSettingsRepository.findFirstBySchId(3)).thenReturn(Optional.empty()); + when(schSettingsRepository.findFirstBySchId(0)) + .thenReturn(Optional.of(row(0, "云海教育平台", null))); + ISchSettingsService.LayerValue hit = service.readLogoTextWithFallback(3); + assertEquals("云海教育平台", hit.getValue()); + assertEquals(0, hit.getHitSchId()); + } +}