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 3a5d9d0..08dcd43 100644
--- a/src/main/java/com/yunhai/userhome/controller/api/SchSettingsController.java
+++ b/src/main/java/com/yunhai/userhome/controller/api/SchSettingsController.java
@@ -1,10 +1,15 @@
package com.yunhai.userhome.controller.api;
+import com.yunhai.platform.utils.exception.DException;
import com.yunhai.platform.utils.model.BoaUser;
import com.yunhai.userhome.config.Config;
import com.yunhai.userhome.entity.SchSettings;
+import com.yunhai.userhome.entity.UserCurrentRole;
import com.yunhai.userhome.service.ISchSettingsService;
+import com.yunhai.userhome.service.IUserCurrentRoleService;
+import com.yunhai.userhome.utils.OtherUtil;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -12,20 +17,34 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Optional;
/**
* 学校设置接口(CAS 登录态,路径 {@code /{platform}/}),对齐旧 UserHome SchSettingsController。
*
getSchSettings 查本地 sch_settings 表(不再依赖跨仓 schConfig);
* saveSchTheme/saveSchLogo 写本地 sch_settings 表。
+ * 主题设置轮(2026-09-30)新增学校默认主题档三接口(契约见 YschTheme《主题设置与通道改动-执行方案》§3.5):
+ * schTheme 读(已登录可读,可空不兜底——与 getSchSettings 的兜底语义刻意区分);
+ * saveSchThemeState 写(仅学校管理员/超管);clearSchTheme 清空(置空 theme 字段——sch_settings 行还承载
+ * logo 等字段不能删行,置空后读侧返回 null,效果等同"未配置")。存储形态:theme_name 列存紧凑四段编码
+ * {@code theme|sideDeep01|gradOn01|sideFollow01}(如 teal|0|1|1;不拆列、零 DDL,列宽未确认故不存整份 JSON;
+ * 旧格式值/非法值读侧一律视同未配置)。
*/
@RestController
@RequestMapping(value = Config.API_PATH)
public class SchSettingsController {
+ /** 合法档位(与通道 THEME_NAMES 一致;防旧库脏值意外生效) */
+ private static final String[] THEME_KEYS = {"blue", "ocean", "ink", "violet", "teal"};
+
@Autowired
private ISchSettingsService schSettingsService;
+ @Autowired
+ private IUserCurrentRoleService userCurrentRoleService;
+
private Integer resolveSchId(BoaUser user) {
if (user.getSchool() != null) {
return user.getSchool().getId();
@@ -36,6 +55,114 @@ public class SchSettingsController {
return 0;
}
+ /** 主题档的目标学校:超管=显式 schId 或 0(平台默认层);其余=当前角色学校(兜底主归属) */
+ private Integer resolveThemeSchId(BoaUser user, Integer explicitSchId) {
+ if (user.getType() != null && user.getType() == 1) {
+ return explicitSchId == null ? 0 : explicitSchId;
+ }
+ return userCurrentRoleService.findById(user.getId())
+ .map(UserCurrentRole::getSchId)
+ .filter(schId -> schId != null && schId > 0)
+ .orElseGet(() -> resolveSchId(user));
+ }
+
+ /** 主题档写权限:超管或学校管理员(先例:UserSettingsServiceImpl.findSchWallpaperManageList) */
+ private void requireThemeAdmin(BoaUser user) {
+ boolean superAdmin = user.getType() != null && user.getType() == 1;
+ if (!superAdmin) {
+ Optional currentRole = userCurrentRoleService.findById(user.getId());
+ if (!currentRole.isPresent() || !OtherUtil.isAdminById(currentRole.get().getRoleId())) {
+ throw new DException("仅学校管理员可配置本校默认主题");
+ }
+ }
+ }
+
+ /** 紧凑编码 → ThemeState JSON;非本编码形态(旧档名/脏值/字段非法)一律 null(视同未配置) */
+ private static String decodeThemeState(String encoded) {
+ if (!StringUtils.hasText(encoded)) return null;
+ String[] parts = encoded.trim().split("\\|");
+ if (parts.length != 4) return null;
+ if (!isThemeKey(parts[0])) return null;
+ for (int i = 1; i < 4; i++) {
+ if (parts[i].length() != 1 || (parts[i].charAt(0) != '0' && parts[i].charAt(0) != '1')) return null;
+ }
+ return "{\"theme\":\"" + parts[0] + "\",\"sideDeep\":" + parts[1]
+ + ",\"gradOn\":" + parts[2] + ",\"sideFollow\":" + parts[3] + "}";
+ }
+
+ /** ThemeState JSON 校验(四字段齐全、档名合法)+ → 紧凑编码;非法抛业务异常(写入侧从严) */
+ private static String encodeThemeState(String themeStateJson) {
+ if (!StringUtils.hasText(themeStateJson)) throw new DException("主题档不能为空");
+ com.fasterxml.jackson.databind.JsonNode n;
+ try {
+ n = new com.fasterxml.jackson.databind.ObjectMapper().readTree(themeStateJson);
+ } catch (Exception e) {
+ throw new DException("主题档格式不合法");
+ }
+ if (n == null || !n.isObject() || !n.hasNonNull("theme") || !n.has("sideDeep") || !n.has("gradOn") || !n.has("sideFollow")) {
+ throw new DException("主题档格式不合法");
+ }
+ String theme = n.get("theme").asText();
+ if (!isThemeKey(theme)) throw new DException("主题档名不合法: " + theme);
+ return theme + "|" + (n.get("sideDeep").asBoolean() ? 1 : 0)
+ + "|" + (n.get("gradOn").asBoolean() ? 1 : 0)
+ + "|" + (n.get("sideFollow").asBoolean() ? 1 : 0);
+ }
+
+ private static boolean isThemeKey(String theme) {
+ if (theme == null) return false;
+ for (String k : THEME_KEYS) {
+ if (k.equals(theme)) return true;
+ }
+ return false;
+ }
+
+ /**
+ * 学校默认主题档(已登录可读):{themeState: "" | null, schId}。
+ * 未配置/旧格式值/脏数据 → themeState=null(前端"不写键、不报错")。
+ */
+ @GetMapping("schTheme")
+ public Map schTheme(BoaUser authUserWithDept) {
+ Integer schId = resolveThemeSchId(authUserWithDept, null);
+ String themeState = schSettingsService.findBySchIdRaw(schId)
+ .map(SchSettings::getThemeName)
+ .map(SchSettingsController::decodeThemeState)
+ .orElse(null);
+ Map out = new HashMap<>();
+ out.put("themeState", themeState);
+ out.put("schId", schId);
+ return out;
+ }
+
+ /** 保存学校默认主题档(仅学校管理员/超管):themeState 为整份 ThemeState JSON,服务端转紧凑编码存 theme_name。 */
+ @PostMapping("saveSchThemeState")
+ public void saveSchThemeState(BoaUser authUserWithDept,
+ @RequestParam("themeState") String themeState,
+ @RequestParam(name = "schId", required = false) Integer schId) {
+ requireThemeAdmin(authUserWithDept);
+ Integer targetSchId = resolveThemeSchId(authUserWithDept, schId);
+ String encoded = encodeThemeState(themeState);
+ SchSettings schSettings = schSettingsService.findBySchIdRaw(targetSchId).orElseGet(SchSettings::new);
+ schSettings.setCDate(new Date());
+ schSettings.setSchId(targetSchId);
+ schSettings.setCId(authUserWithDept.getId());
+ schSettings.setThemeName(encoded);
+ schSettingsService.save(schSettings);
+ }
+
+ /** 清空学校默认主题(仅管理员):置空 theme 字段(行还承载 logo 等配置不能删行;置空后读侧返回 null)。 */
+ @PostMapping("clearSchTheme")
+ public void clearSchTheme(BoaUser authUserWithDept,
+ @RequestParam(name = "schId", required = false) Integer schId) {
+ requireThemeAdmin(authUserWithDept);
+ Integer targetSchId = resolveThemeSchId(authUserWithDept, schId);
+ Optional existing = schSettingsService.findBySchIdRaw(targetSchId);
+ if (!existing.isPresent()) return;
+ SchSettings schSettings = existing.get();
+ schSettings.setThemeName(null);
+ 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/service/ISchSettingsService.java b/src/main/java/com/yunhai/userhome/service/ISchSettingsService.java
index f318d36..ec0742d 100644
--- a/src/main/java/com/yunhai/userhome/service/ISchSettingsService.java
+++ b/src/main/java/com/yunhai/userhome/service/ISchSettingsService.java
@@ -12,5 +12,8 @@ public interface ISchSettingsService {
Optional findBySchId(Integer schId);
+ /** 原样读(无兜底):学校默认主题档等"可空语义"场景专用——查无返回 empty,不合成默认对象 */
+ Optional findBySchIdRaw(Integer schId);
+
List findBySchIds(List schIds);
}
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 d36243e..cb73406 100644
--- a/src/main/java/com/yunhai/userhome/service/impl/SchSettingsServiceImpl.java
+++ b/src/main/java/com/yunhai/userhome/service/impl/SchSettingsServiceImpl.java
@@ -23,6 +23,11 @@ public class SchSettingsServiceImpl implements ISchSettingsService {
schSettingsRepository.save(schSettings);
}
+ @Override
+ public Optional findBySchIdRaw(Integer schId) {
+ return schSettingsRepository.findFirstBySchId(schId);
+ }
+
@Override
public Optional findBySchId(Integer schId) {
Optional local = schSettingsRepository.findFirstBySchId(schId);