用户状态(UserStatus) 和 用户类型(UserType) 应该如何在领域模型中表达?

用户状态(UserStatus) 和 用户类型(UserType) 应该如何在领域模型中表达?

这是一个很有代表性的建模问题:用户状态(UserStatus) 和 用户类型(UserType) 应该如何在领域模型中表达?

✅ 最佳实践结论(DDD 建模角度):

✅ 建议建模为单独的值对象(Value Object),而不是直接使用 Integer 类型。

接下来分析一下为什么

✅ 原因分析:为什么不用普通的 Integer?

问题使用 Integer 的弊端
可读性差status == 1 到底代表啥?新手难以理解
安全性差任何地方都可以传入不合法值,如 status = 999
违反封装User 实体暴露了状态内部的具体编码实现
业务语义不明确Integer 不能表达“启用/冻结/注销”等领域含义

✅ 为什么用值对象更合适?

在 DDD 中,值对象(Value Object)是具有领域语义但无身份标识的对象,适合表达用户的“状态”、“类型”、“级别”等固定枚举值的场景。

它能提供:

✅ 明确的领域语言(如:UserStatus.ACTIVE、UserType.ADMIN)
✅ 不变性(状态是不可变的值对象)
✅ 合法性控制(通过私有构造 + 工厂控制范围)
✅ 方便扩展(可以加业务方法,如 canLogin())

将用户状态设置为值对象以后,我们能明确的表达状态的含义,是正常冻结还是封禁等等。且避免了非法值。并且如果要变更的话,是变更整个值对象。

在领域建模中,你可以一下子明白这个实体中这个值的含义。

在DDD中,不要使用传统的数据建模思想,而是要使用领域建模思想。

传统的数据建模思想的话,用户状态存储在数据库中通常都是1,2,3,4这样的Int值。

所以实体中一般也直接使用Int类型的值作为存储。

领域建模思想则强调将真实世界中的业务映射成领域模型。数据库仅仅是领域模型持久化的一个地方而已。

✅ 示例:建模用户状态为值对象

package com.example.accountbook.domain.model.user;

import java.util.Objects;

public class UserStatus {

    private final int code;

    public static final UserStatus ACTIVE = new UserStatus(1);
    public static final UserStatus FROZEN = new UserStatus(2);
    public static final UserStatus DELETED = new UserStatus(3);

    private UserStatus(int code) {
        this.code = code;
    }

    public static UserStatus of(int code) {
        return switch (code) {
            case 1 -> ACTIVE;
            case 2 -> FROZEN;
            case 3 -> DELETED;
            default -> throw new IllegalArgumentException("非法用户状态: " + code);
        };
    }

    public boolean isActive() {
        return this == ACTIVE;
    }

    public int getCode() {
        return code;
    }

    // equals / hashCode for value object identity

    @Override
    public boolean equals(Object o) {
        return (o instanceof UserStatus that) && this.code == that.code;
    }

    @Override
    public int hashCode() {
        return Objects.hash(code);
    }
}

聚合中使用值对象

public class User {
    private Long id;
    private String username;
    private String passwordHash;

    private UserStatus status;  // ✅ 状态是值对象
    private UserType type;      // ✅ 类型是值对象

}

用 enum 枚举 + 值对象混合的高级方案

一般来说,用户状态和用户类型,我们都会创建一个枚举类。因此我们可以使用枚举+值对象的方式来重新构建一下用户状态。

我们现在深入 DDD 实践:用 Enum 枚举 + 值对象(Value Object)混合建模方式 表达如用户状态 / 用户类型,并展示如何在 MyBatis 中映射为字段、在 Repository 中自动转换为领域值对象。

✅ 为什么用「Enum + 值对象」组合建模?

对象类型作用
enum表达离散枚举值(常量集合)
Value Object表达领域语义 + 方法(如 isActive()

🌟 组合起来,既有类型安全、又有语义表达、也便于持久化。

✅ 枚举类:UserStatusEnum

public enum UserStatusEnum {
    ACTIVE(1, "启用"),
    FROZEN(2, "冻结"),
    DELETED(3, "注销");

    private final int code;
    private final String label;

    UserStatusEnum(int code, String label) {
        this.code = code;
        this.label = label;
    }

    public int getCode() {
        return code;
    }

    public static UserStatusEnum fromCode(int code) {
        for (UserStatusEnum status : values()) {
            if (status.code == code) return status;
        }
        throw new IllegalArgumentException("未知状态: " + code);
    }
}

✅ 值对象:UserStatus

public class UserStatus {

    private final UserStatusEnum status;

    private UserStatus(UserStatusEnum status) {
        this.status = status;
    }

    public static UserStatus of(UserStatusEnum status) {
        return new UserStatus(status);
    }

    public static UserStatus of(int code) {
        return new UserStatus(UserStatusEnum.fromCode(code));
    }

    public boolean isActive() {
        return this.status == UserStatusEnum.ACTIVE;
    }

    public int getCode() {
        return this.status.getCode();
    }

    public String getLabel() {
        return this.status.name();
    }

    public UserStatusEnum getStatusEnum() {
        return this.status;
    }

    @Override
    public boolean equals(Object o) {
        return (o instanceof UserStatus that) && this.status == that.status;
    }

    @Override
    public int hashCode() {
        return status.hashCode();
    }
}

✅ MyBatis 持久化对象(PO):UserPO

public class UserPO {
    private Long id;
    private String username;
    private String passwordHash;
    private Integer status; // 状态值字段
    // ... 省略 getter/setter
}

✅ Mapper XML 映射

<mapper namespace="com.example.repository.mybatis.UserMapper">
    <select id="selectByUsername" resultType="com.example.repository.mybatis.UserPO">
        SELECT id, username, password_hash, status
        FROM users
        WHERE username = #{username}
    </select>
</mapper>

✅ 5. 仓储实现:UserRepositoryImpl → 转换 PO → 聚合

@Repository
public class UserRepositoryImpl implements UserRepository {

    private final UserMapper mapper;

    public UserRepositoryImpl(UserMapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public Optional<User> findByUsername(String username) {
        UserPO userPO = mapper.selectByUsername(username);
        return Optional.ofNullable(userPO).map(this::toEntity);
    }

    private User toEntity(UserPO doObj) {
        return new User(
            doObj.getId(),
            doObj.getUsername(),
            doObj.getPasswordHash(),
            UserStatus.of(doObj.getStatus()) // ← 关键转换点
        );
    }
}

✅ 领域聚合实体:User

public class User {
    private Long id;
    private String username;
    private String passwordHash;
    private UserStatus status; // 值对象

    public User(Long id, String username, String passwordHash, UserStatus status) {
        this.id = id;
        this.username = username;
        this.passwordHash = passwordHash;
        this.status = status;
    }

    public boolean canLogin() {
        return status.isActive();
    }

    public int getStatusCode() {
        return status.getCode();
    }

    public String getStatusLabel() {
        return status.getLabel();
    }

    // 省略其他 getter...
}

✅ 保存数据时:实体 → PO

UserPO userPO = new UserPO();
userPO.setId(user.getId());
userPO.setUsername(user.getUsername());
userPO.setPasswordHash(user.getPasswordHash());
userPO.setStatus(user.getStatus().getCode()); // ← 值对象 → DB 字段

✅ 总结对比

IntegerenumValue Object + enum ✅ 推荐
类型安全
语义表达力⚠️ 限
是否可添加行为⚠️(有限)✅(如 canLogin()
可维护性一般
考试内容具体要求: 项目主题: “智慧校园微服务”系统分析与设计 背景: 假设学校计划开发一个名为“智慧校园微服务”的集成平台,旨在通过一系列松耦合的微服务提升校园管理效率师生体验。该平台初期拟包含以下核心服务模块: 课程管理服务: 课程发布、选课、课表查询、课程资料共享。 校园活动服务: 活动发布、报名、签到、评价。 简易图书借阅服务: 图书检索、在线预约、借阅状态查询、到期提醒。 校园通知服务: 重要公告、课程变动、活动提醒等信息的精准推送。 用户中心服务: 统一的用户身份认证、角色管理(学生、教师、管理员)、基本信息维护。 你的任务: 作为系统分析师设计师,你需要运用UML建模技术,完成对该“智慧校园微服务”(SCM) 平台的核心业务分析与初步架构设计。请围绕选定的1个核心服务模块(从上述1-4中任选其一)以及用户中心服务(第5项,作为必需的基础服务)进行详细建模。 要求与考查点: 第一部分:需求分析与业务建模 1、用例图: 清晰识别并定义你所选核心服务模块以及用户中心服务的主要功能需求。 准确识别参与者(Actors),如:学生、教师、管理员等。 正确绘制用例(Use Cases),体现核心业务功能。 合理使用包含(>)、扩展(>)、泛化关系。 2、活动图: 针对所选核心服务模块中一个关键且相对复杂的业务流程(例如:选课流程、活动报名与签到流程、图书借阅流程、通知发布审批流程)绘制活动图。 清晰展示活动、决策点、分支、合并、泳道(区分不同参与者的职责)。 体现流程的开始、结束主要控制流。 第二部分:静态结构建模 1、类图: 基于需求分析,识别并定义所选核心服务模块以及用户中心服务的核心领域概念(实体类)。 绘制详细的类图,包含: 类名、主要属性(名称、类型、可见性)。 类之间的核心关联关系(名称、多重性、角色名)。 适当的泛化/继承关系(如果存在)。 聚合()、组合()关系(如果适用且能清晰表达语义)。 注意:类图应体现模块内的核心业务对象及其关系。 第三部分:动态行为建模 1、序列图: 针对所选核心服务模块中一个关键用例(例如:学生选课、教师发布活动、学生借阅图书、管理员发送通知)绘制序列图。 清晰展示对象(Object Lifelines)之间的交互消息(Messages),包括同步消息、异步消息、返回消息。 体现消息的顺序、调用关系以及对象的创建与销毁(如果涉及)。 2、状态图: 选择所选核心服务模块或用户中心服务中一个具有明显状态变迁的核心对象(例如:课程(选课状态)、活动(活动状态)、借阅记录(借阅状态)、通知(发送状态)、用户账户(激活/禁用状态))绘制状态图。 清晰定义状态(States)、转移(Transitions)、事件(Events)、守卫条件(Guard Conditions)、动作(Actions)。 准确描述对象在其生命周期内状态的变化规律。 第四部分:架构设计与模型整合 (10%) 1、组件图 : 绘制一个高层级的组件图(Component Diagram) ,展示整个“智慧校园微服务”(SCM) 平台的初步物理或逻辑架构。 在图中清晰标识出: 你所选的核心服务模块 (作为一个组件)用户中心服务 (作为一个组件/包)。 至少1个其他题目中提到的服务模块(如课程管理、活动服务等)(作为组件)。 它们之间的依赖关系(----->) 或 接口提供的需求关系(如果使用组件图且定义了接口)。 此图旨在体现微服务架构的“服务自治”“松耦合”思想。 第五部分:文档与说明 (贯穿始终) 在报告中对每个模型图进行清晰、简洁的文字说明,解释图的意图、关键元素的设计理由表达的业务或设计含义。 保持模型之间的一致性(例如,序列图中的对象应来源于类图,用例图中的用例在活动图/序列图中应得到体现)。 模型图应整洁、规范、易读(合理布局,避免线条交叉过多)。 报告结构清晰,排版专业。 完成简易图书借阅系统服务及用户中心服务
06-12
### 添加或扩展用户信息字段的方法 在 `ruoyi-vue-pro` 项目中,若需添加或扩展用户信息字段,需要从数据库、后端代码前端页面三个层面进行修改调整。以下是具体的实现步骤: #### 数据库表结构修改 首先,在数据库的 `sys_user` 表中新增字段,例如可以添加一个 `user_age` 字段表示年龄: ```sql ALTER TABLE sys_user ADD COLUMN user_age INT COMMENT '用户年龄'; ``` 此外,也可以根据需求添加其他字段,如 `user_hobby`(兴趣)等,并为每个字段设置适当的注释说明[^3]。 #### 后端代码修改 在后端部分,需要更新实体类 `User` 对应的 Mapper 文件,以包含新字段。例如,在 `User.java` 中添加如下字段: ```java private Integer userAge; ``` 接着,在 `UserMapper.xml` 文件中,更新插入语句以包含新的字段: ```xml <insert id="insertUser"> INSERT INTO sys_user (username, password, nick_name, user_type, email, phonenumber, sex, avatar, status, del_flag, login_ip, login_date, create_by, create_time, update_by, update_time, user_age) VALUES (#{username}, #{password}, #{nickName}, #{userType}, #{email}, #{phonenumber}, #{sex}, #{avatar}, #{status}, #{delFlag}, #{loginIp}, #{loginDate}, #{createBy}, #{createTime}, #{updateBy}, #{updateTime}, #{userAge}) </insert> ``` 同时,还需要在服务层控制层对新增字段进行处理,确保数据能够正确传递并持久化到数据库中 [^1]。 #### 前端页面修改 在前端页面中,需要在用户管理模块的相关组件中添加输入框或其他UI元素来展示编辑新增的字段。例如,在 `user/index.vue` 组件中添加一个新的输入框: ```html <el-form-item label="年龄" prop="userAge"> <el-input v-model.number="form.userAge" autocomplete="off" /> </el-form-item> ``` 此外,还需要在表格列定义中添加相应的列,以便显示新增字段的数据: ```html <el-table-column prop="userAge" label="年龄"></el-table-column> ``` 通过以上步骤,可以在 `ruoyi-vue-pro` 项目中成功添加或扩展用户信息字段,满足特定业务需求 [^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值