Skip to content

数据模型与DTO

概述

数据模型与DTO(Data Transfer Object)是ruoyi-plus-uniapp中用于数据传输和业务交互的核心组件。本模块提供了统一的数据传输规范、响应体系和各种业务模型,确保系统各层之间的数据交互规范化和类型安全。

模块结构

text
domain/
├── R.java                    # 统一响应体
├── dto/                      # 数据传输对象
│   ├── UserDTO.java         # 用户数据传输对象
│   ├── DeptDTO.java         # 部门数据传输对象
│   ├── RoleDTO.java         # 角色数据传输对象
│   ├── PostDTO.java         # 岗位数据传输对象
│   ├── DictTypeDTO.java     # 字典类型DTO
│   ├── DictDataDTO.java     # 字典数据DTO
│   ├── OssDTO.java          # 对象存储DTO
│   ├── PaymentDTO.java      # 支付配置DTO
│   ├── PlatformDTO.java     # 平台配置DTO
│   └── UserOnlineDTO.java   # 在线用户DTO
├── model/                    # 业务模型
│   ├── LoginUser.java       # 登录用户模型
│   ├── LoginBody.java       # 登录基础模型
│   ├── PasswordLoginBody.java   # 密码登录模型
│   ├── SmsLoginBody.java        # 短信登录模型
│   ├── EmailLoginBody.java      # 邮箱登录模型
│   ├── SocialLoginBody.java     # 社交登录模型
│   ├── PlatformLoginBody.java   # 平台登录模型
│   └── RegisterBody.java       # 注册模型
└── vo/                       # 视图对象
    └── DictItemVo.java      # 字典项视图对象

统一响应体系

R<T> 统一响应类

R<T> 是系统的统一响应封装类,提供标准化的API响应格式,支持泛型和国际化。

基本结构

java
@Data
@NoArgsConstructor
public class R<T> implements Serializable {
    
    /**
     * 成功状态码 (200)
     */
    public static final int SUCCESS = HttpStatus.SUCCESS;
    
    /**
     * 失败状态码 (500)
     */
    public static final int FAIL = HttpStatus.ERROR;
    
    /**
     * 消息状态码
     */
    private int code;
    
    /**
     * 消息内容(支持国际化)
     */
    private String msg;
    
    /**
     * 数据对象
     */
    private T data;
}

核心特性

自动国际化支持

java
// 自动判断是否为国际化键
private static String processMessage(String message, Object... args) {
    if (StringUtils.isBlank(message)) {
        return null;
    }
    // 使用正则表达式判断国际化键格式
    if (RegexValidator.isValidI18nKey(message)) {
        return MessageUtils.message(message, args);
    }
    return message;
}

链式操作支持

java
// 成功响应
R<User> result = R.ok(user);
R<List<User>> result = R.ok("查询成功", userList);

// 失败响应
R<Void> result = R.fail("操作失败");
R<Void> result = R.fail("user.not.found", userId);

// 条件响应
R<Void> result = R.status(count > 0);
R<Void> result = R.status(success, "操作成功", "操作失败");

使用示例

java
@RestController
public class UserController {
    
    @GetMapping("/getUser/{id}")
    public R<UserVo> getUser(@PathVariable Long id) {
        UserVo user = userService.get(id);
        return R.ok(user);
    }
    
    @PostMapping("/addUser")
    public R<Long> addUser(@RequestBody UserBo bo) {
        Long userId = userService.add(bo);
        return R.ok("user.create.success", userId);
    }
    
    @DeleteMapping("/deleteUser/{id}")
    public R<Void> deleteUser(@PathVariable Long id) {
        boolean success = userService.delete(id);
        return R.status(success, "删除成功", "删除失败");
    }
}

响应格式示例

json
// 成功响应
{
    "code": 200,
    "msg": "操作成功",
    "data": {
        "userId": 1,
        "userName": "admin"
    }
}

// 失败响应
{
    "code": 500,
    "msg": "用户不存在",
    "data": null
}

// 列表响应
{
    "code": 200,
    "msg": "查询成功", 
    "data": [
        {"userId": 1, "userName": "admin"},
        {"userId": 2, "userName": "test"}
    ]
}

数据传输对象 (DTO)

DTO 用于不同层之间的数据传输,通常用于Service层返回数据给Controller层。

用户相关 DTO

UserDTO - 用户数据传输对象

java
@Data
@NoArgsConstructor
public class UserDTO implements Serializable {
    
    private Long userId;
    private Long deptId;
    private String userName;
    private String nickName;
    private String userType;
    private String email;
    private String phone;
    private String gender;
    private String status;
    private Date createTime;
    
    // ... 其他字段
}

DeptDTO - 部门数据传输对象

java
@Data
@NoArgsConstructor
public class DeptDTO implements Serializable {
    
    private Long deptId;
    private Long parentId;
    private String deptName;
    
    // ... 其他字段
}

RoleDTO - 角色数据传输对象

java
@Data
@NoArgsConstructor
public class RoleDTO implements Serializable {
    
    private Long roleId;
    private String roleName;
    private String roleKey;
    
    /**
     * 数据范围
     * 1:全部数据权限 
     * 2:自定数据权限 
     * 3:本部门数据权限 
     * 4:本部门及以下数据权限 
     * 5:仅本人数据权限 
     * 6:部门及以下或本人数据权限
     */
    private String dataScope;
    
    // ... 其他字段
}

字典相关 DTO

DictTypeDTO - 字典类型DTO

java
@Data
@NoArgsConstructor
public class DictTypeDTO implements Serializable {
    
    private Long dictId;
    private String dictName;
    private String dictType;
    private String remark;
    
    // ... 其他字段
}

DictDataDTO - 字典数据DTO

java
@Data
@NoArgsConstructor
public class DictDataDTO implements Serializable {
    
    private String dictLabel;
    private String dictValue;
    private String isDefault;
    private String remark;
    
    // ... 其他字段
}

业务相关 DTO

PaymentDTO - 支付配置DTO

java
@Data
public class PaymentDTO implements Serializable {
    
    private Long id;
    private String type;
    private String mchName;
    private String mchId;
    private String mchKey;
    private String apiV3Key;
    private String notifyUrl;
    private String status;
    
    // ============= 便捷方法 =============
    
    /**
     * 判断是否为启用状态
     */
    public boolean isEnabled() {
        return DictEnableStatus.ENABLE.getValue().equals(this.status);
    }
    
    /**
     * 判断是否为微信支付
     */
    public boolean isWechatPayment() {
        return "WECHAT".equals(this.type);
    }
    
    /**
     * 掩码处理商户密钥(用于日志输出)
     */
    public String getMaskedMchKey() {
        if (mchKey == null || mchKey.length() <= 8) {
            return "****";
        }
        return mchKey.substring(0, 4) + "****" + mchKey.substring(mchKey.length() - 4);
    }
}

PlatformDTO - 平台配置DTO

java
@Data
public class PlatformDTO implements Serializable {
    
    private Long id;
    private String type;
    private String name;
    private String appid;
    private String secret;
    private String paymentIds;
    private String status;
    
    /**
     * 检查是否支持指定的支付配置
     */
    public boolean supportsPayment(Long paymentId) {
        return getPaymentIdList().contains(paymentId);
    }
}

业务模型 (Model)

业务模型主要用于封装业务逻辑相关的数据结构,通常包含业务行为和状态信息。

登录用户模型

LoginUser - 登录用户身份权限

java
@Data
@NoArgsConstructor
public class LoginUser implements Serializable {
    
    private String tenantId;
    private Long userId;
    private Long deptId;
    private String token;
    private String userType;
    private Long loginTime;
    private Long expireTime;
    private String ipaddr;
    private Set<String> menuPermission;
    private Set<String> rolePermission;
    private String userName;
    private String nickName;
    private List<RoleDTO> roles;
    private List<PostDTO> posts;
    
    /**
     * 获取登录id
     */
    public String getLoginId() {
        if (userType == null) {
            throw new IllegalArgumentException("用户类型不能为空");
        }
        if (userId == null) {
            throw new IllegalArgumentException("用户ID不能为空");
        }
        return userType + ":" + userId;
    }
}

登录请求模型

LoginBody - 登录基础模型

java
@Data
public class LoginBody implements Serializable {
    
    /**
     * 认证方式
     */
    @NotBlank(message = I18nKeys.Auth.TYPE_REQUIRED)
    private String authType;
    
    /**
     * 验证码
     */
    private String code;
    
    /**
     * 唯一标识
     */
    private String uuid;
}

PasswordLoginBody - 密码登录模型

java
@Data
@EqualsAndHashCode(callSuper = true)
public class PasswordLoginBody extends LoginBody {
    
    @NotBlank(message = I18nKeys.User.USERNAME_REQUIRED)
    @Length(min = 2, max = 30, message = I18nKeys.User.USERNAME_LENGTH_INVALID)
    private String userName;
    
    @NotBlank(message = I18nKeys.User.PASSWORD_REQUIRED)
    @Length(min = 5, max = 30, message = I18nKeys.User.PASSWORD_LENGTH_INVALID)
    private String password;
}

SmsLoginBody - 短信登录模型

java
@Data
@EqualsAndHashCode(callSuper = true)
public class SmsLoginBody extends LoginBody {
    
    @NotBlank(message = I18nKeys.User.PHONE_REQUIRED)
    private String phone;
    
    @NotBlank(message = I18nKeys.VerifyCode.SMS_REQUIRED)
    private String smsCode;
}

EmailLoginBody - 邮箱登录模型

java
@Data
@EqualsAndHashCode(callSuper = true)
public class EmailLoginBody extends LoginBody {
    
    @NotBlank(message = I18nKeys.User.EMAIL_REQUIRED)
    @Email(message = I18nKeys.User.EMAIL_FORMAT_INVALID)
    private String email;
    
    @NotBlank(message = I18nKeys.VerifyCode.EMAIL_REQUIRED)
    private String emailCode;
}

SocialLoginBody - 社交登录模型

java
@Data
@EqualsAndHashCode(callSuper = true)
public class SocialLoginBody extends LoginBody {
    
    @NotBlank(message = I18nKeys.SocialLogin.SOURCE_REQUIRED)
    private String source;
    
    @NotBlank(message = I18nKeys.SocialLogin.AUTH_CODE_REQUIRED)
    private String socialCode;
    
    @NotBlank(message = I18nKeys.SocialLogin.STATE_REQUIRED)
    private String socialState;
}

RegisterBody - 注册模型

java
@Data
@EqualsAndHashCode(callSuper = true)
public class RegisterBody extends LoginBody {
    
    @NotBlank(message = I18nKeys.User.USERNAME_REQUIRED)
    @Length(min = 2, max = 30, message = I18nKeys.User.USERNAME_LENGTH_INVALID)
    private String userName;
    
    @NotBlank(message = I18nKeys.User.PASSWORD_REQUIRED)
    @Length(min = 5, max = 30, message = I18nKeys.User.PASSWORD_LENGTH_INVALID)
    @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d).*$", 
             message = I18nKeys.User.PASSWORD_COMPLEXITY_INVALID)
    private String password;
    
    private String userType;
}

视图对象 (VO)

视图对象主要用于前端展示,通常包含格式化后的数据和额外的展示属性。

DictItemVo - 字典项视图对象

java
@Data
public class DictItemVo implements Serializable {
    
    private String label;
    private String value;
    private String status;
    private String elTagType;
    private String elTagClass;
    
    /**
     * 快速创建字典项
     */
    public static DictItemVo of(String label, String value) {
        DictItemVo item = new DictItemVo();
        item.setLabel(label);
        item.setValue(value);
        return item;
    }
    
    /**
     * 创建带标签类型的字典项
     */
    public static DictItemVo of(String label, String value, String elTagType) {
        DictItemVo item = new DictItemVo();
        item.setLabel(label);
        item.setValue(value);
        item.setElTagType(elTagType);
        return item;
    }
}

设计原则与最佳实践

1. 命名规范

DTO 命名规范

java
// ✅ 正确命名
UserDTO.java          // 用户数据传输对象
DeptDTO.java          // 部门数据传输对象
PaymentDTO.java       // 支付配置数据传输对象

// ❌ 错误命名
User.java             // 容易与实体类混淆
UserInfo.java         // 含义不明确
UserTransfer.java     // 命名冗余

Model 命名规范

java
// ✅ 正确命名
LoginUser.java        // 登录用户模型
LoginBody.java        // 登录请求模型
RegisterBody.java     // 注册请求模型

// ❌ 错误命名
LoginRequest.java     // 不符合Body约定
LoginModel.java       // 含义模糊
LoginParam.java       // 混淆参数概念

VO 命名规范

java
// ✅ 正确命名
UserVo.java           // 用户视图对象
DictItemVo.java       // 字典项视图对象
MenuTreeVo.java       // 菜单树视图对象

// ❌ 错误命名
UserView.java         // 不符合VO约定
UserDisplay.java      // 含义不明确

2. 数据传输层次

Controller Layer

    Bo/Vo Objects    # 业务对象/视图对象

  Service Layer

    DTO Objects      # 数据传输对象

   Entity Objects    # 实体对象

3. 字段设计原则

必要字段

java
@Data
public class UserDTO implements Serializable {
    
    // ✅ 序列化版本号
    @Serial
    private static final long serialVersionUID = 1L;
    
    // ✅ 主键ID
    private Long userId;
    
    // ✅ 核心业务字段
    private String userName;
    private String nickName;
    
    // ✅ 状态字段
    private String status;
    
    // ✅ 时间字段
    private Date createTime;
}

可选字段处理

java
@Data
public class PaymentDTO implements Serializable {
    
    // 基础字段
    private String mchId;
    private String mchKey;
    
    // 可选字段 - 开发环境配置
    private String devNotifyUrl;
    private String devCertPath;
    
    /**
     * 判断是否有开发环境配置
     */
    public boolean hasDevConfig() {
        return (devNotifyUrl != null && !devNotifyUrl.trim().isEmpty())
            || (devCertPath != null && !devCertPath.trim().isEmpty());
    }
    
    /**
     * 获取实际配置(优先使用开发环境)
     */
    public String getActualNotifyUrl(boolean isDev) {
        if (isDev && hasDevConfig()) {
            return devNotifyUrl;
        }
        return notifyUrl;
    }
}

4. 业务方法设计

状态判断方法

java
@Data
public class PaymentDTO implements Serializable {
    
    private String status;
    private String type;
    
    /**
     * 判断是否为启用状态
     */
    public boolean isEnabled() {
        return DictEnableStatus.ENABLE.getValue().equals(this.status);
    }
    
    /**
     * 判断是否为微信支付
     */
    public boolean isWechatPayment() {
        return "WECHAT".equals(this.type);
    }
    
    /**
     * 判断是否配置了APIv3
     */
    public boolean hasApiV3Config() {
        return apiV3Key != null && !apiV3Key.trim().isEmpty()
            && certSerialNo != null && !certSerialNo.trim().isEmpty();
    }
}

数据转换方法

java
@Data
public class PlatformDTO implements Serializable {
    
    private String paymentIds;  // "1,2,3"
    
    /**
     * 获取支付配置ID列表
     */
    public List<Long> getPaymentIdList() {
        if (paymentIds == null || paymentIds.trim().isEmpty()) {
            return Collections.emptyList();
        }
        
        return Arrays.stream(paymentIds.split(","))
            .map(String.class::trim)
            .filter(s -> !s.isEmpty())
            .map(Long::parseLong)
            .collect(Collectors.toList());
    }
    
    /**
     * 设置支付配置ID列表
     */
    public void setPaymentIdList(List<Long> paymentIdList) {
        if (paymentIdList == null || paymentIdList.isEmpty()) {
            this.paymentIds = "";
        } else {
            this.paymentIds = paymentIdList.stream()
                .map(String::valueOf)
                .collect(Collectors.joining(","));
        }
    }
}

5. 安全性考虑

敏感信息掩码

java
@Data
public class PaymentDTO implements Serializable {
    
    private String mchKey;
    private String apiV3Key;
    
    /**
     * 掩码处理商户密钥(用于日志输出)
     */
    public String getMaskedMchKey() {
        if (mchKey == null || mchKey.length() <= 8) {
            return "****";
        }
        return mchKey.substring(0, 4) + "****" + mchKey.substring(mchKey.length() - 4);
    }
    
    @Override
    public String toString() {
        return "PaymentDTO{" +
                "id=" + id +
                ", mchName='" + mchName + '\'' +
                ", mchId='" + mchId + '\'' +
                ", mchKey='" + getMaskedMchKey() + '\'' +
                ", status='" + status + '\'' +
                '}';
    }
}

使用场景与示例

1. Controller 层使用

java
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    /**
     * 查询用户列表
     */
    @GetMapping
    public R<List<UserVo>> listUsers() {
        List<UserDTO> userDTOs = userService.listUsers();
        List<UserVo> userVos = MapstructUtils.convert(userDTOs, UserVo.class);
        return R.ok(userVos);
    }
    
    /**
     * 获取用户详情
     */
    @GetMapping("/{id}")
    public R<UserVo> getUser(@PathVariable Long id) {
        UserDTO userDTO = userService.getUser(id);
        UserVo userVo = MapstructUtils.convert(userDTO, UserVo.class);
        return R.ok(userVo);
    }
    
    /**
     * 创建用户
     */
    @PostMapping
    public R<Long> createUser(@Validated(AddGroup.class) @RequestBody UserBo userBo) {
        UserDTO userDTO = MapstructUtils.convert(userBo, UserDTO.class);
        Long userId = userService.createUser(userDTO);
        return R.ok("用户创建成功", userId);
    }
}

2. Service 层使用

java
@Service
public class UserServiceImpl implements UserService {
    
    /**
     * 创建用户
     */
    @Override
    public Long createUser(UserDTO userDTO) {
        // 参数校验
        ValidatorUtils.validate(userDTO, AddGroup.class);
        
        // DTO 转 Entity
        User user = MapstructUtils.convert(userDTO, User.class);
        
        // 业务逻辑处理
        user.setCreateTime(new Date());
        user.setStatus(DictEnableStatus.ENABLE.getValue());
        
        // 保存用户
        userRepository.save(user);
        
        return user.getUserId();
    }
    
    /**
     * 获取用户信息
     */
    @Override
    public UserDTO getUser(Long userId) {
        User user = userRepository.findById(userId);
        if (user == null) {
            throw ServiceException.of("用户不存在");
        }
        
        // Entity 转 DTO
        UserDTO userDTO = MapstructUtils.convert(user, UserDTO.class);
        
        return userDTO;
    }
}

3. 登录认证使用

java
@RestController
@RequestMapping("/auth")
public class AuthController {
    
    /**
     * 密码登录
     */
    @PostMapping("/login/password")
    public R<LoginResult> passwordLogin(@Validated @RequestBody PasswordLoginBody loginBody) {
        LoginUser loginUser = authService.passwordLogin(loginBody);
        LoginResult result = buildLoginResult(loginUser);
        return R.ok("登录成功", result);
    }
    
    /**
     * 短信登录
     */
    @PostMapping("/login/sms")
    public R<LoginResult> smsLogin(@Validated @RequestBody SmsLoginBody loginBody) {
        LoginUser loginUser = authService.smsLogin(loginBody);
        LoginResult result = buildLoginResult(loginUser);
        return R.ok("登录成功", result);
    }
    
    /**
     * 社交登录
     */
    @PostMapping("/login/social")
    public R<LoginResult> socialLogin(@Validated @RequestBody SocialLoginBody loginBody) {
        LoginUser loginUser = authService.socialLogin(loginBody);
        LoginResult result = buildLoginResult(loginUser);
        return R.ok("登录成功", result);
    }
    
    /**
     * 用户注册
     */
    @PostMapping("/register")
    public R<Void> register(@Validated @RequestBody RegisterBody registerBody) {
        authService.register(registerBody);
        return R.ok("注册成功");
    }
    
    /**
     * 构建登录结果
     */
    private LoginResult buildLoginResult(LoginUser loginUser) {
        LoginResult result = new LoginResult();
        result.setToken(loginUser.getToken());
        result.setExpireTime(loginUser.getExpireTime());
        
        // 用户信息
        UserInfo userInfo = new UserInfo();
        userInfo.setUserId(loginUser.getUserId());
        userInfo.setUserName(loginUser.getUserName());
        userInfo.setNickName(loginUser.getNickName());
        userInfo.setRoles(loginUser.getRoles());
        userInfo.setPermissions(loginUser.getMenuPermission());
        
        result.setUserInfo(userInfo);
        return result;
    }
}

业务验证与扩展功能

1. 业务验证

java
@Data
public class PaymentDTO implements Serializable {
    
    /**
     * 检查必要的配置项是否完整
     */
    public boolean isConfigComplete() {
        // 基础配置检查
        if (mchId == null || mchId.trim().isEmpty()) {
            return false;
        }
        if (mchName == null || mchName.trim().isEmpty()) {
            return false;
        }
        
        // 根据支付类型验证不同的必要参数
        return switch (type) {
            case "wechat" -> 
                mchKey != null && !mchKey.trim().isEmpty()
                && certPath != null && !certPath.trim().isEmpty();
            case "alipay" -> 
                mchKey != null && !mchKey.trim().isEmpty();
            case "balance" -> true;
            default -> false;
        };
    }
    
    /**
     * 获取配置摘要信息(用于日志)
     */
    public String getConfigSummary() {
        StringBuilder summary = new StringBuilder();
        summary.append("商户[").append(mchName).append("]");
        summary.append("(").append(mchId).append(")");
        summary.append(" 类型:").append(type != null ? type : "未知");
        summary.append(" 状态:").append("1".equals(status) ? "启用" : "禁用");
        if (hasApiV3Config()) {
            summary.append(" [支持APIv3]");
        }
        return summary.toString();
    }
}

数据转换工具

MapStruct 对象映射

java
// 基本转换
UserDTO userDTO = MapstructUtils.convert(user, UserDTO.class);
UserVo userVo = MapstructUtils.convert(userDTO, UserVo.class);

// 列表转换
List<UserVo> userVos = MapstructUtils.convert(userDTOs, UserVo.class);

// Map转对象
UserDTO userDTO = MapstructUtils.convert(userMap, UserDTO.class);