feat(iam): 用户工作台快捷入口偏好(显隐+排序) + 建表

This commit is contained in:
DaxPay Dev
2026-07-04 17:02:46 +08:00
parent 6bc8db497b
commit 9742e0fbfe
9 changed files with 268 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
-- 用户工作台快捷入口偏好表: 用户个性化工作台快捷入口(显隐+排序), PC与移动按 client_code 分开管理
CREATE TABLE IF NOT EXISTS "public"."iam_user_dashboard_preference" (
"id" bigint NOT NULL PRIMARY KEY,
"user_id" bigint NOT NULL,
"client_code" varchar(32) NOT NULL,
"entries" jsonb NOT NULL DEFAULT '[]'::jsonb,
"creator" bigint,
"create_time" timestamptz(6),
"last_modifier" bigint,
"last_modified_time" timestamptz(6),
"version" integer NOT NULL DEFAULT 0,
"deleted" boolean NOT NULL DEFAULT false
);
COMMENT ON TABLE "public"."iam_user_dashboard_preference" IS '用户工作台快捷入口偏好';
COMMENT ON COLUMN "public"."iam_user_dashboard_preference"."id" IS '主键';
COMMENT ON COLUMN "public"."iam_user_dashboard_preference"."user_id" IS '用户ID';
COMMENT ON COLUMN "public"."iam_user_dashboard_preference"."client_code" IS '终端编码(WEB/MOBILE), PC与移动分开管理';
COMMENT ON COLUMN "public"."iam_user_dashboard_preference"."entries" IS '已选快捷入口有序序列(纯key数组), 如 ["merchant","notify"]';
COMMENT ON COLUMN "public"."iam_user_dashboard_preference"."creator" IS '创建人ID';
COMMENT ON COLUMN "public"."iam_user_dashboard_preference"."create_time" IS '创建时间(UTC)';
COMMENT ON COLUMN "public"."iam_user_dashboard_preference"."last_modifier" IS '最后修改人ID';
COMMENT ON COLUMN "public"."iam_user_dashboard_preference"."last_modified_time" IS '最后修改时间(UTC)';
COMMENT ON COLUMN "public"."iam_user_dashboard_preference"."version" IS '乐观锁版本号';
COMMENT ON COLUMN "public"."iam_user_dashboard_preference"."deleted" IS '逻辑删除标志';
-- 每用户每终端唯一一份配置(仅在未逻辑删除时生效, 允许删后重建)
CREATE UNIQUE INDEX IF NOT EXISTS "uk_user_das_pref_user_client"
ON "public"."iam_user_dashboard_preference" ("user_id", "client_code")
WHERE "deleted" = false;

View File

@@ -0,0 +1,44 @@
package cn.daxpay.open.platform.iam.controller.dashboardpreference;
import cn.daxpay.open.platform.core.rest.Res;
import cn.daxpay.open.platform.core.rest.result.Result;
import cn.daxpay.open.platform.iam.param.dashboardpreference.QuickEntrySaveParam;
import cn.daxpay.open.platform.iam.result.dashboardpreference.QuickEntryResult;
import cn.daxpay.open.platform.iam.service.dashboardpreference.UserDashboardPreferenceService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
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;
/// # 工作台快捷入口偏好
///
/// 用户个性化工作台快捷入口(显隐 + 排序), PC 与移动端按请求终端自动区分.
/// 每个登录用户均可管理自己的偏好, 无需额外权限码.
@Validated
@Tag(name = "工作台快捷入口")
@RestController
@RequestMapping("/iam/dashboard/quick-entry")
@RequiredArgsConstructor
public class UserDashboardPreferenceController {
private final UserDashboardPreferenceService userDashboardPreferenceService;
@Operation(summary = "查询当前用户的快捷入口序列")
@GetMapping
public Result<QuickEntryResult> find() {
return Res.ok(userDashboardPreferenceService.findCurrent());
}
@Operation(summary = "保存当前用户的快捷入口序列(整体覆盖)")
@PutMapping
public Result<Void> save(@RequestBody @Validated QuickEntrySaveParam param) {
userDashboardPreferenceService.saveCurrent(param.getEntries());
return Res.ok();
}
}

View File

@@ -0,0 +1,15 @@
package cn.daxpay.open.platform.iam.convert.dashboardpreference;
import cn.daxpay.open.platform.iam.entity.dashboardpreference.UserDashboardPreference;
import cn.daxpay.open.platform.iam.result.dashboardpreference.QuickEntryResult;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserDashboardPreferenceConvert {
UserDashboardPreferenceConvert CONVERT = Mappers.getMapper(UserDashboardPreferenceConvert.class);
QuickEntryResult toResult(UserDashboardPreference in);
}

View File

@@ -0,0 +1,22 @@
package cn.daxpay.open.platform.iam.dao.dashboardpreference;
import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager;
import cn.daxpay.open.platform.iam.entity.dashboardpreference.UserDashboardPreference;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/// # 用户工作台快捷入口偏好
///
@Repository
public class UserDashboardPreferenceManager extends BaseManager<UserDashboardPreferenceMapper, UserDashboardPreference> {
/// 按用户 + 终端查询偏好
public Optional<UserDashboardPreference> findByUserAndClient(Long userId, String clientCode) {
return lambdaQuery()
.eq(UserDashboardPreference::getUserId, userId)
.eq(UserDashboardPreference::getClientCode, clientCode)
.oneOpt();
}
}

View File

@@ -0,0 +1,12 @@
package cn.daxpay.open.platform.iam.dao.dashboardpreference;
import cn.daxpay.open.platform.iam.entity.dashboardpreference.UserDashboardPreference;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/// # 用户工作台快捷入口偏好
///
@Mapper
public interface UserDashboardPreferenceMapper extends MPJBaseMapper<UserDashboardPreference> {
}

View File

@@ -0,0 +1,50 @@
package cn.daxpay.open.platform.iam.entity.dashboardpreference;
import cn.daxpay.open.platform.common.mybatisplus.base.MpBaseEntity;
import cn.daxpay.open.platform.common.mybatisplus.function.ToResult;
import cn.daxpay.open.platform.common.mybatisplus.handler.type.StringListTypeHandler;
import cn.daxpay.open.platform.iam.convert.dashboardpreference.UserDashboardPreferenceConvert;
import cn.daxpay.open.platform.iam.result.dashboardpreference.QuickEntryResult;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.List;
/// # 用户工作台快捷入口偏好
///
/// 按用户 + 终端(clientCode)维度存储用户在工作台自定义的快捷入口序列.
/// 仅存已选入口的 key 有序列表, 入口元信息(图标/标题/路由)由前端各端自行维护, 后端不感知.
/// PC 与移动端通过 clientCode 区分, 互不影响.
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName(value = "iam_user_dashboard_preference", autoResultMap = true)
public class UserDashboardPreference extends MpBaseEntity implements ToResult<QuickEntryResult> {
/// 用户ID
private Long userId;
/// 终端编码(WEB / MOBILE), PC 与移动分开管理
private String clientCode;
/// 已选快捷入口有序序列(纯 key 数组), 如 ["merchant","notify","app"]
@TableField(typeHandler = StringListTypeHandler.class)
private List<String> entries;
@Override
public QuickEntryResult toResult() {
return UserDashboardPreferenceConvert.CONVERT.toResult(this);
}
/// 构造新实例(新增用)
public static UserDashboardPreference init(Long userId, String clientCode, List<String> entries) {
return new UserDashboardPreference()
.setUserId(userId)
.setClientCode(clientCode)
.setEntries(entries);
}
}

View File

@@ -0,0 +1,18 @@
package cn.daxpay.open.platform.iam.param.dashboardpreference;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
@Data
@Accessors(chain = true)
@Schema(title = "工作台快捷入口保存参数")
public class QuickEntrySaveParam {
/// 已选快捷入口有序序列(纯 key 数组), 允许空数组表示全部隐藏
@Schema(description = "已选快捷入口有序序列(纯 key 数组), 允许空数组")
private List<String> entries;
}

View File

@@ -0,0 +1,18 @@
package cn.daxpay.open.platform.iam.result.dashboardpreference;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
@Data
@Accessors(chain = true)
@Schema(title = "工作台快捷入口偏好")
public class QuickEntryResult {
/// 已选快捷入口有序序列(纯 key 数组); 为 null 表示用户尚未自定义, 前端使用默认序列
@Schema(description = "已选快捷入口有序序列(纯 key 数组), null 表示未自定义")
private List<String> entries;
}

View File

@@ -0,0 +1,58 @@
package cn.daxpay.open.platform.iam.service.dashboardpreference;
import cn.daxpay.open.platform.capability.auth.util.SecurityUtil;
import cn.daxpay.open.platform.iam.dao.dashboardpreference.UserDashboardPreferenceManager;
import cn.daxpay.open.platform.iam.entity.dashboardpreference.UserDashboardPreference;
import cn.daxpay.open.platform.iam.result.dashboardpreference.QuickEntryResult;
import cn.daxpay.open.platform.iam.service.client.ClientCodeService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
/// # 用户工作台快捷入口偏好
///
/// PC 与移动端通过请求终端(clientCode)区分, 每用户每端一份配置, 互不影响.
/// 仅存储用户已选入口的 key 有序序列, 入口元信息由前端维护.
@Service
@RequiredArgsConstructor
public class UserDashboardPreferenceService {
private final UserDashboardPreferenceManager userDashboardPreferenceManager;
private final ClientCodeService clientCodeService;
/// 查询当前用户在当前终端的快捷入口序列
/// 未自定义时 entries 返回 null, 前端据此使用默认序列
public QuickEntryResult findCurrent() {
Long userId = SecurityUtil.getUserId();
String clientCode = clientCodeService.getClientCode();
return userDashboardPreferenceManager.findByUserAndClient(userId, clientCode)
.map(UserDashboardPreference::toResult)
.orElseGet(() -> new QuickEntryResult().setEntries(null));
}
/// 保存(整体覆盖)当前用户在当前终端的快捷入口序列
@Transactional(rollbackFor = Exception.class)
public void saveCurrent(List<String> entries) {
Long userId = SecurityUtil.getUserId();
String clientCode = clientCodeService.getClientCode();
// 入参为 null 时按空序列处理, 表示全部隐藏
List<String> safeEntries = entries == null ? new ArrayList<>() : entries;
UserDashboardPreference existed = userDashboardPreferenceManager
.findByUserAndClient(userId, clientCode).orElse(null);
if (existed == null) {
// 新增
userDashboardPreferenceManager.save(
UserDashboardPreference.init(userId, clientCode, safeEntries));
}
else {
// 整体覆盖更新
existed.setEntries(safeEntries);
userDashboardPreferenceManager.updateById(existed);
}
}
}