字典枚举 (Dictionary & Enum System)
概述
字典枚举系统是ruoyi-plus-uniapp中的一个重要设计模式,用于管理系统中的各种状态值、类型码和选项数据。该系统通过枚举类的方式提供类型安全、易于维护的字典数据管理,同时支持前端组件的数据绑定和国际化。
设计理念
核心原则
- 类型安全: 使用枚举替代魔法字符串和数字
- 统一规范: 所有字典都遵循相同的设计模式
- 易于维护: 集中管理,便于修改和扩展
- 前端友好: 提供标准的标签-值对结构
- 国际化支持: 配合国际化系统使用
设计模式
所有字典枚举都遵循统一的设计模式:
java
@Getter
@AllArgsConstructor
public enum DictXxx {
ITEM1("value1", "标签1"),
ITEM2("value2", "标签2");
public static final String DICT_TYPE = "dict_type_code";
private final String value;
private final String label;
// 标准方法
public static DictXxx getByValue(String value) { }
public static DictXxx getByLabel(String label) { }
public static boolean isXxx(String value) { }
}系统内置字典
1. 用户相关字典
用户性别 (DictUserGender)
java
@Getter
@AllArgsConstructor
public enum DictUserGender {
FEMALE("0", "女"),
MALE("1", "男"),
UNKNOWN("2", "未知");
public static final String DICT_TYPE = "sys_user_gender";
private final String value;
private final String label;
}使用场景:
- 用户注册/编辑性别选择
- 用户列表性别显示
- 统计报表性别分组
用户类型 (UserType)
java
@Getter
@AllArgsConstructor
public enum UserType {
PC_USER("pc", "pc_user", 43200, 604800),
MOBILE_USER("mobile", "mobile_user", 172800, 2592000);
private final String deviceType;
private final String userType;
private final int activeTimeout;
private final int timeout;
}特点:
- 针对不同设备类型的用户体系
- 包含会话超时配置
- 支持多端登录管理
2. 状态相关字典
启用状态 (DictEnableStatus)
java
@Getter
@AllArgsConstructor
public enum DictEnableStatus {
ENABLE("1", "启用"),
DISABLED("0", "禁用");
public static final String DICT_TYPE = "sys_enable_status";
// 便捷方法
public static boolean isEnabled(String value) {
return ENABLE.getValue().equals(value);
}
public static boolean isDisabled(String value) {
return DISABLED.getValue().equals(value);
}
}显示设置 (DictDisplaySetting)
java
@Getter
@AllArgsConstructor
public enum DictDisplaySetting {
SHOW("1", "显示"),
HIDE("0", "隐藏");
public static boolean isShow(String value) {
return SHOW.getValue().equals(value);
}
public static boolean isHide(String value) {
return HIDE.getValue().equals(value);
}
}逻辑标志 (DictBooleanFlag)
java
@Getter
@AllArgsConstructor
public enum DictBooleanFlag {
YES("1", "是"),
NO("0", "否");
public static boolean isYes(String value) {
return YES.getValue().equals(value);
}
public static boolean isNo(String value) {
return NO.getValue().equals(value);
}
}3. 业务流程字典
审核状态 (DictAuditStatus)
java
@Getter
@AllArgsConstructor
public enum DictAuditStatus {
PENDING("0", "待审核"),
APPROVED("1", "通过"),
REJECTED("2", "驳回"),
DENIED("3", "拒绝");
public static final String DICT_TYPE = "sys_audit_status";
public static boolean isApproved(String value) {
return APPROVED.getValue().equals(value);
}
public static boolean isPending(String value) {
return PENDING.getValue().equals(value);
}
}订单状态 (DictOrderStatus)
java
@Getter
@AllArgsConstructor
public enum DictOrderStatus {
PENDING("pending", "待支付"),
PAID("paid", "已支付"),
DELIVERED("delivered", "已发货"),
COMPLETED("completed", "已完成"),
CANCELLED("cancelled", "已取消"),
REFUNDED("refunded", "已退款");
// 业务逻辑方法
public boolean canPay() {
return this == PENDING;
}
public boolean canCancel() {
return this == PENDING;
}
public boolean canDeliver() {
return this == PAID;
}
public boolean canComplete() {
return this == DELIVERED;
}
public boolean canRefund() {
return this == PAID || this == DELIVERED;
}
public boolean isFinished() {
return this == COMPLETED || this == CANCELLED || this == REFUNDED;
}
}4. 支付相关字典
支付方式 (DictPaymentMethod)
java
@Getter
@AllArgsConstructor
public enum DictPaymentMethod {
WECHAT("wechat", "微信支付"),
ALIPAY("alipay", "支付宝"),
UNIONPAY("unionpay", "银联"),
BALANCE("balance", "余额支付"),
POINTS("points", "积分抵扣");
public static final String DICT_TYPE = "sys_payment_method";
public static boolean isOnlinePayment(String value) {
return WECHAT.getValue().equals(value) ||
ALIPAY.getValue().equals(value) ||
UNIONPAY.getValue().equals(value);
}
}5. 平台相关字典
平台类型 (DictPlatformType)
java
@Getter
@AllArgsConstructor
public enum DictPlatformType {
MP_WEIXIN("mp-weixin", "微信小程序"),
MP_OFFICIAL_ACCOUNT("mp-official-account", "微信公众号"),
MP_QQ("mp-qq", "QQ小程序"),
MP_ALIPAY("mp-alipay", "支付宝小程序"),
MP_JD("mp-jd", "京东小程序"),
MP_KUAISHOU("mp-kuaishou", "快手小程序"),
MP_LARK("mp-lark", "飞书小程序"),
MP_BAIDU("mp-baidu", "百度小程序"),
MP_TOUTIAO("mp-toutiao", "头条小程序"),
MP_XHS("mp-xhs", "小红书小程序");
public static boolean isWechatPlatform(String value) {
return MP_WEIXIN.getValue().equals(value) ||
MP_OFFICIAL_ACCOUNT.getValue().equals(value);
}
}消息类型 (DictMessageType)
java
@Getter
@AllArgsConstructor
public enum DictMessageType {
SYSTEM("system", "系统通知"),
ACTIVITY("activity", "活动通知"),
AUDIT("audit", "审核通知"),
ACCOUNT("account", "账户通知"),
PRIVATE("private", "私信");
public static boolean isSystemLevel(String value) {
return SYSTEM.getValue().equals(value) ||
AUDIT.getValue().equals(value) ||
ACCOUNT.getValue().equals(value);
}
}6. 系统操作字典
操作类型 (DictOperType)
java
@Getter
@AllArgsConstructor
public enum DictOperType {
INSERT("1", "新增"),
UPDATE("2", "修改"),
DELETE("3", "删除"),
GRANT("4", "授权"),
EXPORT("5", "导出"),
IMPORT("6", "导入"),
FORCE("7", "强退"),
GENCODE("8", "生成代码"),
CLEAN("9", "清空数据"),
OTHER("99", "其他");
public static final String DICT_TYPE = "sys_oper_type";
}操作结果 (DictOperResult)
java
@Getter
@AllArgsConstructor
public enum DictOperResult {
SUCCESS("1", "成功"),
FAIL("0", "失败");
public static final String DICT_TYPE = "sys_oper_result";
}7. 文件相关字典
文件类型 (DictFileType)
java
@Getter
@AllArgsConstructor
public enum DictFileType {
IMAGE("image", "图片"),
DOCUMENT("document", "文档"),
VIDEO("video", "视频"),
AUDIO("audio", "音频"),
ARCHIVE("archive", "压缩包"),
OTHER("other", "其他");
public static boolean isMediaFile(String value) {
return IMAGE.getValue().equals(value) ||
VIDEO.getValue().equals(value) ||
AUDIO.getValue().equals(value);
}
}8. 通知相关字典
通知类型 (DictNoticeType)
java
@Getter
@AllArgsConstructor
public enum DictNoticeType {
NOTICE("1", "通知"),
ANNOUNCEMENT("2", "公告");
public static boolean isNotice(String value) {
return NOTICE.getValue().equals(value);
}
public static boolean isAnnouncement(String value) {
return ANNOUNCEMENT.getValue().equals(value);
}
}通知状态 (DictNoticeStatus)
java
@Getter
@AllArgsConstructor
public enum DictNoticeStatus {
SEND_IMMEDIATELY("1", "立即发送"),
SAVE_AS_DRAFT("0", "保存为草稿");
public static boolean isSendImmediately(String value) {
return SEND_IMMEDIATELY.getValue().equals(value);
}
public static boolean isDraft(String value) {
return SAVE_AS_DRAFT.getValue().equals(value);
}
}认证类型枚举
认证类型 (AuthType)
java
@Getter
@AllArgsConstructor
public enum AuthType {
PASSWORD(I18nKeys.User.PASSWORD_RETRY_LOCKED, I18nKeys.User.PASSWORD_RETRY_COUNT),
SMS(I18nKeys.VerifyCode.SMS_RETRY_LOCKED, I18nKeys.VerifyCode.SMS_RETRY_COUNT),
EMAIL(I18nKeys.VerifyCode.EMAIL_RETRY_LOCKED, I18nKeys.VerifyCode.EMAIL_RETRY_COUNT),
MINIAPP("", ""),
MP("", ""),
SOCIAL("", "");
private final String retryLimitExceed;
private final String retryLimitCount;
}特点:
- 与国际化系统集成
- 包含重试限制配置
- 支持多种认证方式
配合校验使用
字典值校验注解
java
@DictPattern(dictType = "sys_user_sex", message = "性别字典值无效")
private String gender;
@DictPattern(dictType = "sys_user_status", separator = ";", message = "状态值无效")
private String statusList;枚举值校验注解
java
@EnumPattern(type = DictUserGender.class, fieldName = "value", message = "性别值无效")
private String gender;
@EnumPattern(type = DictOrderStatus.class, fieldName = "value")
private String orderStatus;前端使用
Vue组件绑定
vue
<template>
<el-select v-model="form.gender" placeholder="请选择性别">
<el-option
v-for="item in genderOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
<script>
export default {
data() {
return {
genderOptions: [
{ value: '0', label: '女' },
{ value: '1', label: '男' },
{ value: '2', label: '未知' }
]
}
}
}
</script>字典标签组件
vue
<template>
<el-tag
:type="getTagType(status)"
:class="getTagClass(status)"
>
{{ getDictLabel('sys_user_status', status) }}
</el-tag>
</template>最佳实践
1. 命名规范
java
// 字典枚举类命名
DictXxx // 通用字典
Dict + 业务域 + 类型名
// 示例
DictUserGender // 用户性别
DictOrderStatus // 订单状态
DictPaymentMethod // 支付方式2. 结构规范
java
@Getter
@AllArgsConstructor
public enum DictExample {
// 枚举项定义
ITEM1("value1", "标签1"),
ITEM2("value2", "标签2");
// 字典类型常量
public static final String DICT_TYPE = "dict_type_code";
// 基础字段
private final String value;
private final String label;
// 标准查找方法
public static DictExample getByValue(String value) {
for (DictExample item : values()) {
if (item.getValue().equals(value)) {
return item;
}
}
return DEFAULT_VALUE; // 或抛出异常
}
public static DictExample getByLabel(String label) {
for (DictExample item : values()) {
if (item.getLabel().equals(label)) {
return item;
}
}
return DEFAULT_VALUE;
}
// 业务判断方法
public static boolean isSpecialType(String value) {
return ITEM1.getValue().equals(value);
}
}3. 异常处理策略
策略一:返回默认值
java
public static DictUserGender getByValue(String value) {
for (DictUserGender gender : values()) {
if (gender.getValue().equals(value)) {
return gender;
}
}
return UNKNOWN; // 返回默认值
}策略二:抛出异常
java
public static DictPaymentMethod getByValue(String value) {
for (DictPaymentMethod method : values()) {
if (method.getValue().equals(value)) {
return method;
}
}
throw new ServiceException(StringUtils.format("不支持的支付类型:{}", value));
}4. 业务逻辑集成
java
// 在枚举中包含业务逻辑
public enum DictOrderStatus {
// ...枚举定义
// 业务状态判断
public boolean canPay() {
return this == PENDING;
}
// 状态流转验证
public boolean canTransitionTo(DictOrderStatus target) {
return switch (this) {
case PENDING -> target == PAID || target == CANCELLED;
case PAID -> target == DELIVERED || target == REFUNDED;
case DELIVERED -> target == COMPLETED;
default -> false;
};
}
}5. 缓存优化
java
// 使用静态缓存提高查找性能
public enum DictExample {
// 枚举定义...
private static final Map<String, DictExample> VALUE_MAP =
Arrays.stream(values())
.collect(Collectors.toMap(DictExample::getValue, Function.identity()));
public static DictExample getByValue(String value) {
return VALUE_MAP.getOrDefault(value, DEFAULT_VALUE);
}
}扩展字典
1. 添加新字典
java
@Getter
@AllArgsConstructor
public enum DictCustomType {
TYPE1("1", "类型1"),
TYPE2("2", "类型2");
public static final String DICT_TYPE = "custom_type";
private final String value;
private final String label;
// 实现标准方法...
}2. 复杂字典支持
java
@Getter
@AllArgsConstructor
public enum DictComplexType {
ITEM1("value1", "标签1", "描述1", "#ff0000"),
ITEM2("value2", "标签2", "描述2", "#00ff00");
private final String value;
private final String label;
private final String description;
private final String color;
// 扩展查找方法
public static List<DictComplexType> getByColor(String color) {
return Arrays.stream(values())
.filter(item -> item.getColor().equals(color))
.collect(Collectors.toList());
}
}3. 分组字典
java
@Getter
@AllArgsConstructor
public enum DictGroupedType {
GROUP1_ITEM1("g1_i1", "组1项1", "group1"),
GROUP1_ITEM2("g1_i2", "组1项2", "group1"),
GROUP2_ITEM1("g2_i1", "组2项1", "group2");
private final String value;
private final String label;
private final String group;
// 按组查找
public static List<DictGroupedType> getByGroup(String group) {
return Arrays.stream(values())
.filter(item -> item.getGroup().equals(group))
.collect(Collectors.toList());
}
}国际化支持
1. 标签国际化
java
@Getter
@AllArgsConstructor
public enum DictI18nType {
ITEM1("value1", "dict.i18n.type.item1"),
ITEM2("value2", "dict.i18n.type.item2");
private final String value;
private final String labelKey; // 国际化键
public String getLocalizedLabel() {
return MessageUtils.message(labelKey);
}
}2. 配置文件
properties
# messages_zh_CN.properties
dict.i18n.type.item1=项目1
dict.i18n.type.item2=项目2
# messages_en_US.properties
dict.i18n.type.item1=Item 1
dict.i18n.type.item2=Item 2数据库同步
1. 字典数据初始化
sql
-- 初始化字典类型
INSERT INTO sys_dict_type (dict_type, dict_name, status)
VALUES ('sys_user_gender', '用户性别', '0');
-- 初始化字典数据
INSERT INTO sys_dict_data (dict_type, dict_label, dict_value, dict_sort, status)
VALUES
('sys_user_gender', '男', '1', 1, '0'),
('sys_user_gender', '女', '0', 2, '0'),
('sys_user_gender', '未知', '2', 3, '0');2. 同步脚本
java
@Component
public class DictSyncService {
public void syncEnumToDatabase() {
// 遍历所有字典枚举
for (DictUserGender gender : DictUserGender.values()) {
// 同步到数据库
syncDictData(DictUserGender.DICT_TYPE,
gender.getValue(),
gender.getLabel());
}
}
}枚举设计模式
策略模式与枚举结合
枚举非常适合实现策略模式,每个枚举项代表一种策略实现:
java
/**
* 计价策略枚举
*/
@Getter
@AllArgsConstructor
public enum PricingStrategy {
/**
* 正常价格策略
*/
NORMAL("normal", "正常价格") {
@Override
public BigDecimal calculate(BigDecimal price, int quantity) {
return price.multiply(BigDecimal.valueOf(quantity));
}
},
/**
* 会员折扣策略
*/
VIP("vip", "会员折扣") {
@Override
public BigDecimal calculate(BigDecimal price, int quantity) {
return price.multiply(BigDecimal.valueOf(quantity))
.multiply(BigDecimal.valueOf(0.8));
}
},
/**
* 满减策略
*/
FULL_REDUCTION("full_reduction", "满减活动") {
@Override
public BigDecimal calculate(BigDecimal price, int quantity) {
BigDecimal total = price.multiply(BigDecimal.valueOf(quantity));
// 满100减20
if (total.compareTo(BigDecimal.valueOf(100)) >= 0) {
return total.subtract(BigDecimal.valueOf(20));
}
return total;
}
},
/**
* 阶梯定价策略
*/
TIERED("tiered", "阶梯定价") {
@Override
public BigDecimal calculate(BigDecimal price, int quantity) {
// 阶梯定价:1-10件原价,11-50件9折,50件以上8折
if (quantity <= 10) {
return price.multiply(BigDecimal.valueOf(quantity));
} else if (quantity <= 50) {
BigDecimal first10 = price.multiply(BigDecimal.valueOf(10));
BigDecimal rest = price.multiply(BigDecimal.valueOf(0.9))
.multiply(BigDecimal.valueOf(quantity - 10));
return first10.add(rest);
} else {
BigDecimal first10 = price.multiply(BigDecimal.valueOf(10));
BigDecimal mid40 = price.multiply(BigDecimal.valueOf(0.9))
.multiply(BigDecimal.valueOf(40));
BigDecimal rest = price.multiply(BigDecimal.valueOf(0.8))
.multiply(BigDecimal.valueOf(quantity - 50));
return first10.add(mid40).add(rest);
}
}
};
private final String value;
private final String label;
/**
* 计算价格的抽象方法
*/
public abstract BigDecimal calculate(BigDecimal price, int quantity);
/**
* 根据值获取策略
*/
public static PricingStrategy getByValue(String value) {
for (PricingStrategy strategy : values()) {
if (strategy.getValue().equals(value)) {
return strategy;
}
}
return NORMAL;
}
}使用示例:
java
@Service
@RequiredArgsConstructor
public class OrderPricingService {
/**
* 计算订单总价
*/
public BigDecimal calculateOrderPrice(Order order, String strategyType) {
PricingStrategy strategy = PricingStrategy.getByValue(strategyType);
BigDecimal totalPrice = BigDecimal.ZERO;
for (OrderItem item : order.getItems()) {
BigDecimal itemPrice = strategy.calculate(
item.getUnitPrice(),
item.getQuantity()
);
totalPrice = totalPrice.add(itemPrice);
}
return totalPrice;
}
}工厂模式与枚举结合
使用枚举实现工厂模式,创建不同类型的对象:
java
/**
* 消息发送器工厂枚举
*/
@Getter
@AllArgsConstructor
public enum MessageSenderFactory {
SMS("sms", "短信发送") {
@Override
public MessageSender createSender() {
return new SmsSender();
}
},
EMAIL("email", "邮件发送") {
@Override
public MessageSender createSender() {
return new EmailSender();
}
},
WECHAT("wechat", "微信消息") {
@Override
public MessageSender createSender() {
return new WechatSender();
}
},
APP_PUSH("app_push", "APP推送") {
@Override
public MessageSender createSender() {
return new AppPushSender();
}
};
private final String value;
private final String label;
/**
* 创建发送器的抽象方法
*/
public abstract MessageSender createSender();
/**
* 根据类型获取发送器
*/
public static MessageSender getSender(String type) {
for (MessageSenderFactory factory : values()) {
if (factory.getValue().equals(type)) {
return factory.createSender();
}
}
throw new ServiceException("不支持的消息类型: " + type);
}
/**
* 批量创建发送器
*/
public static List<MessageSender> createSenders(List<String> types) {
return types.stream()
.map(MessageSenderFactory::getSender)
.collect(Collectors.toList());
}
}
/**
* 消息发送器接口
*/
public interface MessageSender {
void send(String target, String content);
boolean supports(String target);
}
/**
* 短信发送器实现
*/
public class SmsSender implements MessageSender {
@Override
public void send(String target, String content) {
// 调用短信接口发送
log.info("发送短信到: {}, 内容: {}", target, content);
}
@Override
public boolean supports(String target) {
return target.matches("^1[3-9]\\d{9}$");
}
}状态机模式与枚举结合
使用枚举实现状态机,管理状态流转:
java
/**
* 工单状态枚举(状态机实现)
*/
@Getter
@AllArgsConstructor
public enum WorkOrderStatus {
CREATED("created", "已创建") {
@Override
public Set<WorkOrderStatus> allowedTransitions() {
return EnumSet.of(ASSIGNED, CANCELLED);
}
},
ASSIGNED("assigned", "已分配") {
@Override
public Set<WorkOrderStatus> allowedTransitions() {
return EnumSet.of(IN_PROGRESS, CANCELLED, CREATED);
}
},
IN_PROGRESS("in_progress", "处理中") {
@Override
public Set<WorkOrderStatus> allowedTransitions() {
return EnumSet.of(PENDING_REVIEW, ASSIGNED);
}
},
PENDING_REVIEW("pending_review", "待审核") {
@Override
public Set<WorkOrderStatus> allowedTransitions() {
return EnumSet.of(COMPLETED, IN_PROGRESS);
}
},
COMPLETED("completed", "已完成") {
@Override
public Set<WorkOrderStatus> allowedTransitions() {
return EnumSet.noneOf(WorkOrderStatus.class);
}
},
CANCELLED("cancelled", "已取消") {
@Override
public Set<WorkOrderStatus> allowedTransitions() {
return EnumSet.noneOf(WorkOrderStatus.class);
}
};
private final String value;
private final String label;
/**
* 获取允许流转的目标状态
*/
public abstract Set<WorkOrderStatus> allowedTransitions();
/**
* 检查是否可以流转到目标状态
*/
public boolean canTransitionTo(WorkOrderStatus target) {
return allowedTransitions().contains(target);
}
/**
* 执行状态流转
*/
public WorkOrderStatus transitionTo(WorkOrderStatus target) {
if (!canTransitionTo(target)) {
throw new ServiceException(
String.format("状态[%s]不能流转到[%s]", this.label, target.label)
);
}
return target;
}
/**
* 是否为终态
*/
public boolean isFinalState() {
return allowedTransitions().isEmpty();
}
/**
* 是否为活跃状态
*/
public boolean isActive() {
return this == IN_PROGRESS || this == PENDING_REVIEW;
}
/**
* 根据值获取状态
*/
public static WorkOrderStatus getByValue(String value) {
for (WorkOrderStatus status : values()) {
if (status.getValue().equals(value)) {
return status;
}
}
throw new ServiceException("未知的工单状态: " + value);
}
}使用示例:
java
@Service
@RequiredArgsConstructor
public class WorkOrderService {
private final WorkOrderRepository workOrderRepository;
/**
* 工单状态变更
*/
@Transactional
public void changeStatus(Long workOrderId, String targetStatus) {
WorkOrder workOrder = workOrderRepository.findById(workOrderId)
.orElseThrow(() -> new ServiceException("工单不存在"));
WorkOrderStatus currentStatus = WorkOrderStatus.getByValue(workOrder.getStatus());
WorkOrderStatus newStatus = WorkOrderStatus.getByValue(targetStatus);
// 状态机验证并流转
WorkOrderStatus finalStatus = currentStatus.transitionTo(newStatus);
workOrder.setStatus(finalStatus.getValue());
workOrder.setStatusLabel(finalStatus.getLabel());
workOrder.setUpdateTime(LocalDateTime.now());
workOrderRepository.save(workOrder);
// 发布状态变更事件
publishStatusChangeEvent(workOrder, currentStatus, finalStatus);
}
}动态字典系统
数据库字典与枚举结合
在实际项目中,需要将静态枚举与动态数据库字典结合使用:
java
/**
* 字典数据服务
*/
@Service
@RequiredArgsConstructor
public class DictDataService {
private final SysDictDataMapper dictDataMapper;
private final RedissonClient redissonClient;
/**
* 字典缓存前缀
*/
private static final String DICT_CACHE_PREFIX = "dict:data:";
/**
* 获取字典数据列表
*/
public List<SysDictData> getDictByType(String dictType) {
String cacheKey = DICT_CACHE_PREFIX + dictType;
RList<SysDictData> cachedList = redissonClient.getList(cacheKey);
if (cachedList.isExists() && !cachedList.isEmpty()) {
return cachedList.readAll();
}
// 从数据库加载
LambdaQueryWrapper<SysDictData> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysDictData::getDictType, dictType)
.eq(SysDictData::getStatus, "0")
.orderByAsc(SysDictData::getDictSort);
List<SysDictData> dataList = dictDataMapper.selectList(wrapper);
// 写入缓存
if (CollUtil.isNotEmpty(dataList)) {
cachedList.addAll(dataList);
cachedList.expire(Duration.ofHours(24));
}
return dataList;
}
/**
* 获取字典标签
*/
public String getDictLabel(String dictType, String dictValue) {
List<SysDictData> dataList = getDictByType(dictType);
return dataList.stream()
.filter(data -> data.getDictValue().equals(dictValue))
.map(SysDictData::getDictLabel)
.findFirst()
.orElse("");
}
/**
* 获取字典值
*/
public String getDictValue(String dictType, String dictLabel) {
List<SysDictData> dataList = getDictByType(dictType);
return dataList.stream()
.filter(data -> data.getDictLabel().equals(dictLabel))
.map(SysDictData::getDictValue)
.findFirst()
.orElse("");
}
/**
* 刷新字典缓存
*/
public void refreshCache(String dictType) {
String cacheKey = DICT_CACHE_PREFIX + dictType;
redissonClient.getList(cacheKey).delete();
getDictByType(dictType); // 重新加载
}
/**
* 刷新所有字典缓存
*/
public void refreshAllCache() {
redissonClient.getKeys().deleteByPattern(DICT_CACHE_PREFIX + "*");
}
}字典包装器
将枚举与数据库字典统一为相同接口:
java
/**
* 字典项接口
*/
public interface DictItem {
String getValue();
String getLabel();
default String getDescription() { return ""; }
default Integer getSort() { return 0; }
default String getCssClass() { return ""; }
default String getListClass() { return ""; }
}
/**
* 枚举字典包装器
*/
public class EnumDictWrapper<E extends Enum<E>> {
private final Class<E> enumClass;
private final Function<E, String> valueExtractor;
private final Function<E, String> labelExtractor;
public EnumDictWrapper(
Class<E> enumClass,
Function<E, String> valueExtractor,
Function<E, String> labelExtractor) {
this.enumClass = enumClass;
this.valueExtractor = valueExtractor;
this.labelExtractor = labelExtractor;
}
/**
* 获取所有字典项
*/
public List<DictItem> getAllItems() {
return Arrays.stream(enumClass.getEnumConstants())
.map(this::toDictItem)
.collect(Collectors.toList());
}
/**
* 转换为字典项
*/
private DictItem toDictItem(E enumValue) {
return new DictItem() {
@Override
public String getValue() {
return valueExtractor.apply(enumValue);
}
@Override
public String getLabel() {
return labelExtractor.apply(enumValue);
}
@Override
public Integer getSort() {
return enumValue.ordinal();
}
};
}
/**
* 根据值查找枚举
*/
public E getByValue(String value) {
for (E enumValue : enumClass.getEnumConstants()) {
if (valueExtractor.apply(enumValue).equals(value)) {
return enumValue;
}
}
return null;
}
/**
* 根据标签查找枚举
*/
public E getByLabel(String label) {
for (E enumValue : enumClass.getEnumConstants()) {
if (labelExtractor.apply(enumValue).equals(label)) {
return enumValue;
}
}
return null;
}
}使用示例:
java
@Configuration
public class DictConfig {
@Bean
public EnumDictWrapper<DictUserGender> userGenderWrapper() {
return new EnumDictWrapper<>(
DictUserGender.class,
DictUserGender::getValue,
DictUserGender::getLabel
);
}
@Bean
public EnumDictWrapper<DictOrderStatus> orderStatusWrapper() {
return new EnumDictWrapper<>(
DictOrderStatus.class,
DictOrderStatus::getValue,
DictOrderStatus::getLabel
);
}
}混合字典服务
统一处理枚举字典和数据库字典:
java
/**
* 混合字典服务
*/
@Service
@RequiredArgsConstructor
public class HybridDictService {
private final DictDataService dictDataService;
private final Map<String, EnumDictWrapper<?>> enumWrappers;
/**
* 枚举字典类型映射
*/
private static final Map<String, Class<?>> ENUM_DICT_TYPES = Map.of(
"sys_user_gender", DictUserGender.class,
"sys_order_status", DictOrderStatus.class,
"sys_payment_method", DictPaymentMethod.class
);
/**
* 获取字典数据(自动判断来源)
*/
public List<DictItem> getDictItems(String dictType) {
// 优先从枚举获取
EnumDictWrapper<?> wrapper = enumWrappers.get(dictType);
if (wrapper != null) {
return wrapper.getAllItems();
}
// 从数据库获取
return dictDataService.getDictByType(dictType)
.stream()
.map(this::toDictItem)
.collect(Collectors.toList());
}
/**
* 获取字典标签
*/
public String getDictLabel(String dictType, String value) {
List<DictItem> items = getDictItems(dictType);
return items.stream()
.filter(item -> item.getValue().equals(value))
.map(DictItem::getLabel)
.findFirst()
.orElse("");
}
/**
* 批量获取字典标签
*/
public Map<String, String> getDictLabels(String dictType, Collection<String> values) {
List<DictItem> items = getDictItems(dictType);
Map<String, String> labelMap = items.stream()
.collect(Collectors.toMap(DictItem::getValue, DictItem::getLabel));
return values.stream()
.distinct()
.collect(Collectors.toMap(
Function.identity(),
value -> labelMap.getOrDefault(value, "")
));
}
private DictItem toDictItem(SysDictData data) {
return new DictItem() {
@Override
public String getValue() { return data.getDictValue(); }
@Override
public String getLabel() { return data.getDictLabel(); }
@Override
public String getDescription() { return data.getRemark(); }
@Override
public Integer getSort() { return data.getDictSort(); }
@Override
public String getCssClass() { return data.getCssClass(); }
@Override
public String getListClass() { return data.getListClass(); }
};
}
}字典与Excel导入导出
Excel字典转换器
在Excel导入导出时,自动转换字典值和标签:
java
/**
* Excel字典转换器
*/
public class ExcelDictConverter implements Converter<String> {
private static HybridDictService dictService;
/**
* 设置字典服务(通过静态注入)
*/
public static void setDictService(HybridDictService service) {
dictService = service;
}
private final String dictType;
public ExcelDictConverter(String dictType) {
this.dictType = dictType;
}
@Override
public Class<?> supportJavaTypeKey() {
return String.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 读取Excel时:标签 -> 值
*/
@Override
public String convertToJavaData(ReadCellData<?> cellData,
ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
String label = cellData.getStringValue();
if (StringUtils.isBlank(label)) {
return "";
}
return dictService.getDictItems(dictType).stream()
.filter(item -> item.getLabel().equals(label))
.map(DictItem::getValue)
.findFirst()
.orElse(label);
}
/**
* 写入Excel时:值 -> 标签
*/
@Override
public WriteCellData<?> convertToExcelData(String value,
ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
if (StringUtils.isBlank(value)) {
return new WriteCellData<>("");
}
String label = dictService.getDictLabel(dictType, value);
return new WriteCellData<>(StringUtils.isNotBlank(label) ? label : value);
}
}字典转换注解
自定义注解简化Excel字典配置:
java
/**
* Excel字典转换注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@ExcelProperty
public @interface ExcelDictField {
/**
* 字典类型
*/
String dictType();
/**
* 列名
*/
String value() default "";
/**
* 列索引
*/
int index() default -1;
}
/**
* 用户导出VO
*/
@Data
public class UserExportVo {
@ExcelProperty("用户名")
private String username;
@ExcelProperty("姓名")
private String nickname;
@ExcelDictField(dictType = "sys_user_gender", value = "性别")
private String gender;
@ExcelDictField(dictType = "sys_enable_status", value = "状态")
private String status;
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}Excel字典处理器
处理字典注解的Excel读写:
java
/**
* 字典Excel处理器
*/
@Component
@RequiredArgsConstructor
public class DictExcelHandler {
private final HybridDictService dictService;
/**
* 导出时处理字典字段
*/
public <T> List<T> processExportData(List<T> dataList) {
if (CollUtil.isEmpty(dataList)) {
return dataList;
}
Class<?> clazz = dataList.get(0).getClass();
Map<Field, String> dictFields = getDictFields(clazz);
for (T data : dataList) {
for (Map.Entry<Field, String> entry : dictFields.entrySet()) {
Field field = entry.getKey();
String dictType = entry.getValue();
try {
field.setAccessible(true);
Object value = field.get(data);
if (value instanceof String) {
String label = dictService.getDictLabel(dictType, (String) value);
field.set(data, StringUtils.isNotBlank(label) ? label : value);
}
} catch (IllegalAccessException e) {
log.warn("字典字段处理失败: {}", field.getName(), e);
}
}
}
return dataList;
}
/**
* 导入时处理字典字段
*/
public <T> List<T> processImportData(List<T> dataList) {
if (CollUtil.isEmpty(dataList)) {
return dataList;
}
Class<?> clazz = dataList.get(0).getClass();
Map<Field, String> dictFields = getDictFields(clazz);
for (T data : dataList) {
for (Map.Entry<Field, String> entry : dictFields.entrySet()) {
Field field = entry.getKey();
String dictType = entry.getValue();
try {
field.setAccessible(true);
Object label = field.get(data);
if (label instanceof String) {
String value = getDictValue(dictType, (String) label);
field.set(data, StringUtils.isNotBlank(value) ? value : label);
}
} catch (IllegalAccessException e) {
log.warn("字典字段处理失败: {}", field.getName(), e);
}
}
}
return dataList;
}
/**
* 获取类的字典字段
*/
private Map<Field, String> getDictFields(Class<?> clazz) {
Map<Field, String> dictFields = new HashMap<>();
for (Field field : clazz.getDeclaredFields()) {
ExcelDictField annotation = field.getAnnotation(ExcelDictField.class);
if (annotation != null) {
dictFields.put(field, annotation.dictType());
}
}
return dictFields;
}
/**
* 根据标签获取值
*/
private String getDictValue(String dictType, String label) {
return dictService.getDictItems(dictType).stream()
.filter(item -> item.getLabel().equals(label))
.map(DictItem::getValue)
.findFirst()
.orElse("");
}
}字典权限控制
根据角色过滤字典项
某些字典项可能需要根据用户角色进行过滤:
java
/**
* 字典权限服务
*/
@Service
@RequiredArgsConstructor
public class DictPermissionService {
private final HybridDictService dictService;
/**
* 字典权限配置
*/
private static final Map<String, Map<String, Set<String>>> DICT_PERMISSIONS = Map.of(
// 订单状态字典:不同角色可见不同状态
"sys_order_status", Map.of(
"admin", Set.of("pending", "paid", "delivered", "completed", "cancelled", "refunded"),
"operator", Set.of("pending", "paid", "delivered", "completed"),
"customer", Set.of("pending", "paid", "delivered", "completed")
),
// 审核状态字典:管理员可见所有,普通用户仅可见部分
"sys_audit_status", Map.of(
"admin", Set.of("0", "1", "2", "3"),
"auditor", Set.of("0", "1", "2"),
"user", Set.of("0", "1")
)
);
/**
* 获取用户可见的字典项
*/
public List<DictItem> getVisibleDictItems(String dictType, String roleKey) {
List<DictItem> allItems = dictService.getDictItems(dictType);
// 检查是否有权限配置
Map<String, Set<String>> rolePermissions = DICT_PERMISSIONS.get(dictType);
if (rolePermissions == null) {
return allItems; // 无权限配置,返回全部
}
// 获取角色可见的值集合
Set<String> visibleValues = rolePermissions.get(roleKey);
if (visibleValues == null) {
visibleValues = rolePermissions.get("default");
}
if (visibleValues == null) {
return allItems; // 无默认配置,返回全部
}
Set<String> finalVisibleValues = visibleValues;
return allItems.stream()
.filter(item -> finalVisibleValues.contains(item.getValue()))
.collect(Collectors.toList());
}
/**
* 检查用户是否可以使用某字典值
*/
public boolean canUseValue(String dictType, String value, String roleKey) {
Map<String, Set<String>> rolePermissions = DICT_PERMISSIONS.get(dictType);
if (rolePermissions == null) {
return true;
}
Set<String> visibleValues = rolePermissions.get(roleKey);
if (visibleValues == null) {
return true;
}
return visibleValues.contains(value);
}
/**
* 获取当前用户可见的字典项
*/
public List<DictItem> getCurrentUserDictItems(String dictType) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser == null) {
return Collections.emptyList();
}
// 获取用户最高权限角色
String roleKey = loginUser.getRoles().stream()
.map(RoleDto::getRoleKey)
.min(Comparator.comparingInt(this::getRolePriority))
.orElse("user");
return getVisibleDictItems(dictType, roleKey);
}
/**
* 角色优先级(越小越高)
*/
private int getRolePriority(String roleKey) {
return switch (roleKey) {
case "admin" -> 1;
case "auditor", "operator" -> 2;
default -> 3;
};
}
}字典接口权限控制
java
/**
* 字典控制器(带权限控制)
*/
@RestController
@RequestMapping("/dict")
@RequiredArgsConstructor
public class DictController {
private final DictPermissionService dictPermissionService;
/**
* 获取字典数据(根据用户角色过滤)
*/
@GetMapping("/data/{dictType}")
public R<List<DictItem>> getDictData(@PathVariable String dictType) {
List<DictItem> items = dictPermissionService.getCurrentUserDictItems(dictType);
return R.ok(items);
}
/**
* 获取字典数据(管理员专用,返回全部)
*/
@GetMapping("/data/all/{dictType}")
@PreAuthorize("@ss.hasRole('admin')")
public R<List<DictItem>> getAllDictData(@PathVariable String dictType) {
List<DictItem> items = dictPermissionService.getVisibleDictItems(dictType, "admin");
return R.ok(items);
}
}字典变更审计
字典变更事件
记录字典数据的变更历史:
java
/**
* 字典变更事件
*/
@Getter
public class DictChangeEvent extends ApplicationEvent {
private final String dictType;
private final String dictValue;
private final ChangeType changeType;
private final Object oldValue;
private final Object newValue;
private final String operator;
private final LocalDateTime changeTime;
public DictChangeEvent(Object source, String dictType, String dictValue,
ChangeType changeType, Object oldValue, Object newValue) {
super(source);
this.dictType = dictType;
this.dictValue = dictValue;
this.changeType = changeType;
this.oldValue = oldValue;
this.newValue = newValue;
this.operator = SecurityUtils.getUsername();
this.changeTime = LocalDateTime.now();
}
public enum ChangeType {
CREATE, UPDATE, DELETE
}
}
/**
* 字典变更监听器
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class DictChangeListener {
private final DictChangeLogMapper changeLogMapper;
@EventListener
@Async
public void handleDictChange(DictChangeEvent event) {
log.info("字典变更: type={}, value={}, changeType={}",
event.getDictType(), event.getDictValue(), event.getChangeType());
// 记录变更日志
DictChangeLog changeLog = new DictChangeLog();
changeLog.setDictType(event.getDictType());
changeLog.setDictValue(event.getDictValue());
changeLog.setChangeType(event.getChangeType().name());
changeLog.setOldValue(JsonUtils.toJsonString(event.getOldValue()));
changeLog.setNewValue(JsonUtils.toJsonString(event.getNewValue()));
changeLog.setOperator(event.getOperator());
changeLog.setChangeTime(event.getChangeTime());
changeLogMapper.insert(changeLog);
}
}字典变更日志表
sql
-- 字典变更日志表
CREATE TABLE sys_dict_change_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
dict_type VARCHAR(100) NOT NULL COMMENT '字典类型',
dict_value VARCHAR(100) COMMENT '字典值',
change_type VARCHAR(20) NOT NULL COMMENT '变更类型(CREATE/UPDATE/DELETE)',
old_value TEXT COMMENT '变更前值(JSON)',
new_value TEXT COMMENT '变更后值(JSON)',
operator VARCHAR(64) NOT NULL COMMENT '操作人',
change_time DATETIME NOT NULL COMMENT '变更时间',
INDEX idx_dict_type (dict_type),
INDEX idx_change_time (change_time)
) COMMENT='字典变更日志表';注意事项
1. 性能考虑
- 使用静态缓存Map提高查找性能
- 避免在循环中频繁调用getByValue方法
- 考虑使用枚举序号进行比较
- 字典数据加载时使用懒加载策略
- 批量查询时使用缓存预热
2. 兼容性
- 新增枚举项时考虑向后兼容
- 删除枚举项需要检查现有数据
- 修改枚举值需要同步更新数据库
- 版本升级时提供数据迁移脚本
3. 维护性
- 保持枚举命名的一致性
- 及时更新相关文档
- 定期检查未使用的枚举项
- 枚举类应包含完整的JavaDoc注释
4. 测试覆盖
java
@Test
public void testDictUserGender() {
// 测试值查找
assertEquals(DictUserGender.MALE, DictUserGender.getByValue("1"));
assertEquals(DictUserGender.UNKNOWN, DictUserGender.getByValue("invalid"));
// 测试标签查找
assertEquals(DictUserGender.FEMALE, DictUserGender.getByLabel("女"));
// 测试业务方法
assertTrue(DictUserGender.MALE.getValue().equals("1"));
}
@Test
public void testWorkOrderStatusTransition() {
// 测试状态机流转
WorkOrderStatus created = WorkOrderStatus.CREATED;
// 可以流转到ASSIGNED
assertTrue(created.canTransitionTo(WorkOrderStatus.ASSIGNED));
// 不能直接流转到COMPLETED
assertFalse(created.canTransitionTo(WorkOrderStatus.COMPLETED));
// 测试流转异常
assertThrows(ServiceException.class, () -> {
created.transitionTo(WorkOrderStatus.COMPLETED);
});
}
@Test
public void testPricingStrategy() {
// 测试不同定价策略
BigDecimal price = BigDecimal.valueOf(100);
int quantity = 5;
assertEquals(BigDecimal.valueOf(500),
PricingStrategy.NORMAL.calculate(price, quantity));
assertEquals(BigDecimal.valueOf(400),
PricingStrategy.VIP.calculate(price, quantity));
}5. 安全性考虑
java
/**
* 字典值安全校验
*/
@Component
public class DictValueValidator {
/**
* 校验字典值是否合法
*/
public boolean isValidValue(String dictType, String value) {
if (StringUtils.isBlank(value)) {
return false;
}
// 检查特殊字符
if (containsSpecialChars(value)) {
return false;
}
// 检查长度
if (value.length() > 100) {
return false;
}
return true;
}
/**
* 检查是否包含危险字符
*/
private boolean containsSpecialChars(String value) {
String pattern = "[<>\"'&;]";
return Pattern.compile(pattern).matcher(value).find();
}
/**
* 清理字典值
*/
public String sanitizeValue(String value) {
if (StringUtils.isBlank(value)) {
return "";
}
return value.replaceAll("[<>\"'&;]", "")
.trim()
.substring(0, Math.min(value.length(), 100));
}
}总结
字典枚举系统为ruoyi-plus-uniapp提供了统一、类型安全的字典数据管理方案,通过规范的设计模式和丰富的内置字典,大大提高了开发效率和代码质量。合理使用字典枚举可以有效避免魔法字符串,提升代码的可读性和维护性。
本文档涵盖了字典枚举的核心功能:
- 基础设计模式 - 统一的枚举结构规范
- 策略模式集成 - 在枚举中实现业务策略
- 工厂模式应用 - 使用枚举创建对象实例
- 状态机实现 - 管理复杂的状态流转逻辑
- 动态字典系统 - 数据库字典与枚举的结合
- Excel集成 - 字典与导入导出的自动转换
- 权限控制 - 根据角色过滤字典项
- 变更审计 - 记录字典数据的变更历史
通过这些功能的合理运用,可以构建出灵活、可维护、高性能的字典管理体系。
