feat(system): 用户主题配置持久化
This commit is contained in:
@@ -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<UserPreferenceThemeRespVO> getTheme() {
|
||||||
|
// 只允许读取当前登录用户自己的主题配置,不提供按 userId 查询他人配置的入口。
|
||||||
|
return success(UserPreferenceThemeRespVO.of(userPreferenceService.getThemeSettings(getLoginUserId())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/theme")
|
||||||
|
@Operation(summary = "保存当前登录用户主题配置")
|
||||||
|
public CommonResult<Boolean> saveTheme(@Valid @RequestBody UserPreferenceThemeSaveReqVO reqVO) {
|
||||||
|
// 保存归属始终以后端登录态为准,禁止前端通过请求体指定目标用户。
|
||||||
|
userPreferenceService.saveThemeSettings(getLoginUserId(), reqVO.getThemeSettings());
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/theme")
|
||||||
|
@Operation(summary = "重置当前登录用户主题配置")
|
||||||
|
public CommonResult<Boolean> resetTheme() {
|
||||||
|
// 重置语义是清空当前用户的个性化覆盖项,前端随后回退到默认主题配置。
|
||||||
|
userPreferenceService.resetThemeSettings(getLoginUserId());
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<String, Object> themeSettings = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public Map<String, Object> getThemeSettings() {
|
||||||
|
return themeSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonAnyGetter
|
||||||
|
public Map<String, Object> any() {
|
||||||
|
return themeSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonAnySetter
|
||||||
|
public void put(String key, Object value) {
|
||||||
|
// 保持响应体扁平化,避免序列化成 { "themeSettings": { ... } } 的额外嵌套。
|
||||||
|
themeSettings.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserPreferenceThemeRespVO of(Map<String, Object> themeSettings) {
|
||||||
|
UserPreferenceThemeRespVO respVO = new UserPreferenceThemeRespVO();
|
||||||
|
if (themeSettings != null) {
|
||||||
|
// 仅复制覆盖项本身,不在后端补默认主题配置,默认值仍由前端维护。
|
||||||
|
respVO.getThemeSettings().putAll(themeSettings);
|
||||||
|
}
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<String, Object> themeSettings = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public Map<String, Object> getThemeSettings() {
|
||||||
|
return themeSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonAnyGetter
|
||||||
|
public Map<String, Object> any() {
|
||||||
|
return themeSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonAnySetter
|
||||||
|
public void put(String key, Object value) {
|
||||||
|
// 将请求体中的任意 JSON 字段收集为主题覆盖项,保持接口形态仍然是“直接 JSON 对象”。
|
||||||
|
themeSettings.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<String, Object> themeSettingsJson;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<UserPreferenceDO> {
|
||||||
|
|
||||||
|
default UserPreferenceDO selectByUserId(Long userId) {
|
||||||
|
// 主题偏好按 user_id 一对一绑定,查询入口只保留按用户读取。
|
||||||
|
return selectOne(UserPreferenceDO::getUserId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
default int updateThemeSettingsByUserId(Long userId, Map<String, Object> themeSettingsJson) {
|
||||||
|
UserPreferenceDO updateObj = new UserPreferenceDO();
|
||||||
|
updateObj.setThemeSettingsJson(themeSettingsJson);
|
||||||
|
// 只更新主题 JSON 字段,避免误改用户归属;允许更新为 null,用于“重置主题”场景。
|
||||||
|
return update(updateObj, new LambdaUpdateWrapper<UserPreferenceDO>()
|
||||||
|
.eq(UserPreferenceDO::getUserId, userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<String, Object> getThemeSettings(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存当前用户主题配置覆盖项
|
||||||
|
*
|
||||||
|
* 约束:
|
||||||
|
* 1. 保存的是用户主题覆盖项,不是完整默认主题;
|
||||||
|
* 2. 前端不允许传 userId 决定归属,归属由调用方基于登录态传入;
|
||||||
|
* 3. 传入空对象时,等价于清空个性化覆盖项。
|
||||||
|
*
|
||||||
|
* @param userId 用户 ID
|
||||||
|
* @param themeSettings 主题配置覆盖项
|
||||||
|
*/
|
||||||
|
void saveThemeSettings(Long userId, @Valid Map<String, Object> themeSettings);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置当前用户主题配置覆盖项
|
||||||
|
*
|
||||||
|
* 约束:
|
||||||
|
* 1. 重置后接口再次读取应返回空对象 {};
|
||||||
|
* 2. 具体实现不要求物理删除记录,但外部语义必须等价于“没有个性化配置”。
|
||||||
|
*
|
||||||
|
* @param userId 用户 ID
|
||||||
|
*/
|
||||||
|
void resetThemeSettings(Long userId);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<String, Object> 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<String, Object> themeSettings) {
|
||||||
|
UserPreferenceDO preference = userPreferenceMapper.selectByUserId(userId);
|
||||||
|
if (MapUtil.isEmpty(themeSettings)) {
|
||||||
|
// 空对象等价于“没有个性化覆盖项”,直接复用重置语义,避免数据库残留无意义的 {}。
|
||||||
|
if (preference != null) {
|
||||||
|
userPreferenceMapper.updateThemeSettingsByUserId(userId, null);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存副本而不是直接持有入参引用,避免后续调用链继续改动同一 Map。
|
||||||
|
Map<String, Object> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -63,7 +63,7 @@ spring:
|
|||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
host: 127.0.0.1 # 地址
|
host: 127.0.0.1 # 地址
|
||||||
port: 6379 # 端口
|
port: 16379 # 端口
|
||||||
database: 1 # 数据库索引
|
database: 1 # 数据库索引
|
||||||
# password: njcnpqs # 密码,建议生产环境开启
|
# password: njcnpqs # 密码,建议生产环境开启
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ if ($LASTEXITCODE -eq 0) {
|
|||||||
throw "当前没有已暂存的改动,请先执行 git add。"
|
throw "当前没有已暂存的改动,请先执行 git add。"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$stagedFiles = @(git -C $repoRoot diff --cached --name-only)
|
||||||
|
|
||||||
$types = @(
|
$types = @(
|
||||||
'feat',
|
'feat',
|
||||||
'feat-wip',
|
'feat-wip',
|
||||||
@@ -82,6 +84,11 @@ $message = "{0}({1}): {2}" -f $type, $scope, $description
|
|||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "提交信息预览:$message"
|
Write-Host "提交信息预览:$message"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "本次将提交以下已暂存文件:"
|
||||||
|
foreach ($file in $stagedFiles) {
|
||||||
|
Write-Host " - $file"
|
||||||
|
}
|
||||||
|
|
||||||
$confirm = Read-Host '确认提交请输入 y,取消请输入其他任意内容'
|
$confirm = Read-Host '确认提交请输入 y,取消请输入其他任意内容'
|
||||||
if ($confirm -ne 'y' -and $confirm -ne 'Y') {
|
if ($confirm -ne 'y' -and $confirm -ne 'Y') {
|
||||||
|
|||||||
Reference in New Issue
Block a user