diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserPreferenceController.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserPreferenceController.java new file mode 100644 index 0000000..0722b0c --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserPreferenceController.java @@ -0,0 +1,54 @@ +package com.njcn.rdms.module.system.controller.admin.user; + +import com.njcn.rdms.framework.common.pojo.CommonResult; +import com.njcn.rdms.module.system.controller.admin.user.vo.preference.UserPreferenceThemeRespVO; +import com.njcn.rdms.module.system.controller.admin.user.vo.preference.UserPreferenceThemeSaveReqVO; +import com.njcn.rdms.module.system.service.user.UserPreferenceService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static com.njcn.rdms.framework.common.pojo.CommonResult.success; +import static com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - 用户主题偏好") +@RestController +@RequestMapping("/system/user-preference") +@Validated +public class UserPreferenceController { + + @Resource + private UserPreferenceService userPreferenceService; + + @GetMapping("/theme") + @Operation(summary = "获得当前登录用户主题配置") + public CommonResult getTheme() { + // 只允许读取当前登录用户自己的主题配置,不提供按 userId 查询他人配置的入口。 + return success(UserPreferenceThemeRespVO.of(userPreferenceService.getThemeSettings(getLoginUserId()))); + } + + @PutMapping("/theme") + @Operation(summary = "保存当前登录用户主题配置") + public CommonResult saveTheme(@Valid @RequestBody UserPreferenceThemeSaveReqVO reqVO) { + // 保存归属始终以后端登录态为准,禁止前端通过请求体指定目标用户。 + userPreferenceService.saveThemeSettings(getLoginUserId(), reqVO.getThemeSettings()); + return success(true); + } + + @DeleteMapping("/theme") + @Operation(summary = "重置当前登录用户主题配置") + public CommonResult resetTheme() { + // 重置语义是清空当前用户的个性化覆盖项,前端随后回退到默认主题配置。 + userPreferenceService.resetThemeSettings(getLoginUserId()); + return success(true); + } + +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/preference/UserPreferenceThemeRespVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/preference/UserPreferenceThemeRespVO.java new file mode 100644 index 0000000..247a73f --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/preference/UserPreferenceThemeRespVO.java @@ -0,0 +1,48 @@ +package com.njcn.rdms.module.system.controller.admin.user.vo.preference; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.LinkedHashMap; +import java.util.Map; + +@Schema(description = "管理后台 - 当前用户主题配置 Response VO") +public class UserPreferenceThemeRespVO { + + /** + * 返回给前端的主题配置覆盖项。 + * + * 后端只返回用户自己保存过的覆盖项; + * 若当前用户尚未保存,则由 Service 保证返回空对象 {}。 + */ + @Schema(description = "主题配置覆盖项") + private final Map themeSettings = new LinkedHashMap<>(); + + @JsonIgnore + public Map getThemeSettings() { + return themeSettings; + } + + @JsonAnyGetter + public Map any() { + return themeSettings; + } + + @JsonAnySetter + public void put(String key, Object value) { + // 保持响应体扁平化,避免序列化成 { "themeSettings": { ... } } 的额外嵌套。 + themeSettings.put(key, value); + } + + public static UserPreferenceThemeRespVO of(Map themeSettings) { + UserPreferenceThemeRespVO respVO = new UserPreferenceThemeRespVO(); + if (themeSettings != null) { + // 仅复制覆盖项本身,不在后端补默认主题配置,默认值仍由前端维护。 + respVO.getThemeSettings().putAll(themeSettings); + } + return respVO; + } + +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/preference/UserPreferenceThemeSaveReqVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/preference/UserPreferenceThemeSaveReqVO.java new file mode 100644 index 0000000..76934aa --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/preference/UserPreferenceThemeSaveReqVO.java @@ -0,0 +1,39 @@ +package com.njcn.rdms.module.system.controller.admin.user.vo.preference; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.LinkedHashMap; +import java.util.Map; + +@Schema(description = "管理后台 - 当前用户主题配置保存 Request VO") +public class UserPreferenceThemeSaveReqVO { + + /** + * 前端传入的主题配置覆盖项。 + * + * 这里故意不声明固定字段,原因是主题配置项是可扩展的; + * 同时不接收 userId 等归属字段,配置归属只以后端登录态识别。 + */ + @Schema(description = "主题配置覆盖项") + private final Map themeSettings = new LinkedHashMap<>(); + + @JsonIgnore + public Map getThemeSettings() { + return themeSettings; + } + + @JsonAnyGetter + public Map any() { + return themeSettings; + } + + @JsonAnySetter + public void put(String key, Object value) { + // 将请求体中的任意 JSON 字段收集为主题覆盖项,保持接口形态仍然是“直接 JSON 对象”。 + themeSettings.put(key, value); + } + +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/dataobject/user/UserPreferenceDO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/dataobject/user/UserPreferenceDO.java new file mode 100644 index 0000000..1778569 --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/dataobject/user/UserPreferenceDO.java @@ -0,0 +1,54 @@ +package com.njcn.rdms.module.system.dal.dataobject.user; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.util.Map; + +/** + * 用户偏好配置 DO + */ +@TableName(value = "system_user_preference", autoResultMap = true) +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserPreferenceDO extends BaseDO { + + /** + * 主键 ID + * + * 由当前项目 MyBatis Plus 的 ASSIGN_ID 策略生成,不要求数据库自增。 + */ + @TableId + private Long id; + + /** + * 用户 ID + * + * 数据库层面对该字段有唯一约束,保证每个用户至多一条偏好记录。 + */ + private Long userId; + + /** + * 用户主题配置覆盖项 + * + * 使用 updateStrategy = ALWAYS,保证重置时可以将 JSON 字段更新为 null。 + * 这里只保存用户覆盖项,不保存前端完整默认主题配置。 + */ + @TableField(typeHandler = JacksonTypeHandler.class, updateStrategy = FieldStrategy.ALWAYS) + private Map themeSettingsJson; + +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/mysql/user/UserPreferenceMapper.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/mysql/user/UserPreferenceMapper.java new file mode 100644 index 0000000..bbad5d1 --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/mysql/user/UserPreferenceMapper.java @@ -0,0 +1,26 @@ +package com.njcn.rdms.module.system.dal.mysql.user; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.module.system.dal.dataobject.user.UserPreferenceDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Map; + +@Mapper +public interface UserPreferenceMapper extends BaseMapperX { + + default UserPreferenceDO selectByUserId(Long userId) { + // 主题偏好按 user_id 一对一绑定,查询入口只保留按用户读取。 + return selectOne(UserPreferenceDO::getUserId, userId); + } + + default int updateThemeSettingsByUserId(Long userId, Map themeSettingsJson) { + UserPreferenceDO updateObj = new UserPreferenceDO(); + updateObj.setThemeSettingsJson(themeSettingsJson); + // 只更新主题 JSON 字段,避免误改用户归属;允许更新为 null,用于“重置主题”场景。 + return update(updateObj, new LambdaUpdateWrapper() + .eq(UserPreferenceDO::getUserId, userId)); + } + +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserPreferenceService.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserPreferenceService.java new file mode 100644 index 0000000..1f234b6 --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserPreferenceService.java @@ -0,0 +1,48 @@ +package com.njcn.rdms.module.system.service.user; + +import jakarta.validation.Valid; + +import java.util.Map; + +/** + * 用户偏好配置 Service 接口 + */ +public interface UserPreferenceService { + + /** + * 获得当前用户主题配置覆盖项 + * + * 约束: + * 1. 只处理当前登录用户自己的配置; + * 2. 若数据库中没有记录,返回空对象 {},而不是 null。 + * + * @param userId 用户 ID + * @return 主题配置覆盖项;未保存时返回空对象 + */ + Map getThemeSettings(Long userId); + + /** + * 保存当前用户主题配置覆盖项 + * + * 约束: + * 1. 保存的是用户主题覆盖项,不是完整默认主题; + * 2. 前端不允许传 userId 决定归属,归属由调用方基于登录态传入; + * 3. 传入空对象时,等价于清空个性化覆盖项。 + * + * @param userId 用户 ID + * @param themeSettings 主题配置覆盖项 + */ + void saveThemeSettings(Long userId, @Valid Map themeSettings); + + /** + * 重置当前用户主题配置覆盖项 + * + * 约束: + * 1. 重置后接口再次读取应返回空对象 {}; + * 2. 具体实现不要求物理删除记录,但外部语义必须等价于“没有个性化配置”。 + * + * @param userId 用户 ID + */ + void resetThemeSettings(Long userId); + +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserPreferenceServiceImpl.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserPreferenceServiceImpl.java new file mode 100644 index 0000000..691ef5b --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserPreferenceServiceImpl.java @@ -0,0 +1,68 @@ +package com.njcn.rdms.module.system.service.user; + +import cn.hutool.core.map.MapUtil; +import com.njcn.rdms.module.system.dal.dataobject.user.UserPreferenceDO; +import com.njcn.rdms.module.system.dal.mysql.user.UserPreferenceMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 用户偏好配置 Service 实现类 + */ +@Service +@Validated +@Slf4j +public class UserPreferenceServiceImpl implements UserPreferenceService { + + @Resource + private UserPreferenceMapper userPreferenceMapper; + + @Override + public Map getThemeSettings(Long userId) { + UserPreferenceDO preference = userPreferenceMapper.selectByUserId(userId); + if (preference == null || MapUtil.isEmpty(preference.getThemeSettingsJson())) { + // 对外固定返回 {},避免前端再区分 null 与空对象两套分支。 + return new LinkedHashMap<>(); + } + // 返回副本,避免上层误改持久化对象里的 Map 引用。 + return new LinkedHashMap<>(preference.getThemeSettingsJson()); + } + + @Override + public void saveThemeSettings(Long userId, Map themeSettings) { + UserPreferenceDO preference = userPreferenceMapper.selectByUserId(userId); + if (MapUtil.isEmpty(themeSettings)) { + // 空对象等价于“没有个性化覆盖项”,直接复用重置语义,避免数据库残留无意义的 {}。 + if (preference != null) { + userPreferenceMapper.updateThemeSettingsByUserId(userId, null); + } + return; + } + + // 保存副本而不是直接持有入参引用,避免后续调用链继续改动同一 Map。 + Map themeSettingsCopy = new LinkedHashMap<>(themeSettings); + if (preference == null) { + // 当前用户还没有偏好记录时创建首条数据,依赖 user_id 唯一约束保证一人一份。 + userPreferenceMapper.insert(UserPreferenceDO.builder() + .userId(userId) + .themeSettingsJson(themeSettingsCopy) + .build()); + return; + } + + // 当前用户已有记录时仅更新主题覆盖项 JSON,不改动归属 userId。 + userPreferenceMapper.updateThemeSettingsByUserId(userId, themeSettingsCopy); + } + + @Override + public void resetThemeSettings(Long userId) { + // 不直接做逻辑删除,避免 user_id 唯一键与逻辑删除组合后影响后续再次保存。 + userPreferenceMapper.updateThemeSettingsByUserId(userId, null); + } + +} diff --git a/rdms-system/rdms-system-boot/src/main/resources/application-local.yaml b/rdms-system/rdms-system-boot/src/main/resources/application-local.yaml index 11bc32e..3b08b4c 100644 --- a/rdms-system/rdms-system-boot/src/main/resources/application-local.yaml +++ b/rdms-system/rdms-system-boot/src/main/resources/application-local.yaml @@ -63,7 +63,7 @@ spring: data: redis: host: 127.0.0.1 # 地址 - port: 6379 # 端口 + port: 16379 # 端口 database: 1 # 数据库索引 # password: njcnpqs # 密码,建议生产环境开启 diff --git a/scripts/commit.ps1 b/scripts/commit.ps1 index b904927..9db3431 100644 --- a/scripts/commit.ps1 +++ b/scripts/commit.ps1 @@ -34,6 +34,8 @@ if ($LASTEXITCODE -eq 0) { throw "当前没有已暂存的改动,请先执行 git add。" } +$stagedFiles = @(git -C $repoRoot diff --cached --name-only) + $types = @( 'feat', 'feat-wip', @@ -82,6 +84,11 @@ $message = "{0}({1}): {2}" -f $type, $scope, $description Write-Host "" Write-Host "提交信息预览:$message" +Write-Host "" +Write-Host "本次将提交以下已暂存文件:" +foreach ($file in $stagedFiles) { + Write-Host " - $file" +} $confirm = Read-Host '确认提交请输入 y,取消请输入其他任意内容' if ($confirm -ne 'y' -and $confirm -ne 'Y') {