feat(壁纸): 三级默认壁纸——sch_settings.wallpaper_url 两级配置 + getUserSettings 兜底链(个人未自选:本校默认→平台默认→池内最新)(LOGO文字与主题JSON化增补)
build / build-push (push) Successful in 1m13s

This commit is contained in:
chenxing
2026-10-09 11:53:18 +08:00
parent f9282574e2
commit 1f352214f7
6 changed files with 166 additions and 3 deletions
@@ -181,6 +181,38 @@ public class SchSettingsController {
schSettingsService.save(schSettings);
}
// ===== 默认壁纸(平台/学校两级,壁纸三级默认轮 2026-10-09):与 LOGO 文字同模式 =====
/** 默认壁纸读(已登录可读):{wallpaperUrl: string|null, schId=实际命中层}。本校空 → 平台层 → null。 */
@GetMapping("schWallpaper")
public Map<String, Object> schWallpaper(BoaUser authUserWithDept) {
ISchSettingsService.LayerValue hit =
schSettingsService.readWallpaperWithFallback(schSettingsService.resolveThemeSchId(authUserWithDept, null));
Map<String, Object> 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) {
@@ -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 行)→ 本地学校壁纸池最新一张 → 平台池最新一张(历史兜底,两级皆未配置时保持原体验)。
* <p>学校壁纸完全本地管理(wallpaper 表 type=1),不再依赖跨仓 schConfig。</p>
*/
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<Wallpaper> wallList = userSettingsService.findWallList(authUserWithDept.getId());
if (schId != null) {
// 1. 本地学校壁纸第一张(type=1,取最新)
// 3. 本地学校壁纸第一张(type=1,取最新)
Optional<Wallpaper> 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<Wallpaper> sysWall = wallList.stream()
.filter(w -> w.getType() != null && w.getType() == 0)
.max(Comparator.comparing(Wallpaper::getId));
@@ -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;
@@ -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 即此语义——
* 前端据此区分「本校已配置」与「本校未配置、正在回落平台默认」。 */
@@ -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)
@@ -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());
}
}