feat(device): 一期设备域 I1-I5 全量实现(APP 接口/后台管理/OTA/MQTT/直播)
模块接入:
- 新增 ruoyi-device(org.dromara.device)并注册到根/ruoyi-modules/ruoyi-admin pom
APP 侧(/app/**,需登录,仅本人绑定设备):
- 账号闭环:短信/邮箱验证码注册登录、密码重置(不跨界返回散列)
- 设备:绑定/列表/详情/重命名/解绑(越权统一"设备不存在",防 IDOR)
- 眼罩开关(/app/device/{deviceNo}/hood,低电不对称拦截)
- 定位(/app/device/{deviceNo}/location)
- 直播(/app/stream 6 端点 + /app/device/{deviceNo}/live,自建 ZLMediaKit,设备主动推流)
后台(/device/**):
- 设备 CRUD/统计/删除/导出(ExcelBuilder)
- OTA(6 端点:升级包管理 + 批量下发,total==created+skipped+failed)
MQTT(T05):
- 遥测/事件/ack 监听、指令下发+回执(防串设备)、离线扫描、低电告警
- 报文身份仅来自 Topic,反查 device_id 防跨设备伪造
数据与脚本:
- script/sql/ry_device.sql(6 表/字典/权限字/菜单)、bm_patch.sql
- script/mqtt 设备模拟器(mqtt-simulate.js)
安全:
- MQTT broker 凭据外置为环境变量 MQTT_USERNAME/MQTT_PASSWORD,不落明文
- ZLM webhook 密钥外置为环境变量 ZLM_SECRET(未配置时 /zlm/hook/** 失败关闭),不落明文
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
package org.dromara.system.api;
|
||||
|
||||
import org.dromara.system.api.domain.AppUserDTO;
|
||||
|
||||
/**
|
||||
* APP 用户(C 端)领域服务对外暴露的 API。
|
||||
* <p>
|
||||
* 设计口径(迭代 I2 边界裁定 · 方案 A):
|
||||
* <ul>
|
||||
* <li>本接口仅面向设备域({@code ruoyi-device})等业务模块,用于 APP 账号闭环(注册/登录/改密/换绑/注销)。</li>
|
||||
* <li><b>密码绝不跨模块边界</b>:本接口不返回、不接收任何已加密的密码散列;
|
||||
* 校验密码由 {@link #matchesPassword(Long, String)} 在 {@code ruoyi-system} 内部完成并只回传布尔值,
|
||||
* 写入密码由 {@link #registerByPhone(String, String, String)} / {@link #resetPassword(Long, String)}
|
||||
* 在 {@code ruoyi-system} 内部完成散列(BCrypt)后落库。</li>
|
||||
* <li>本接口为<b>纯新增</b>,不改动既有 {@link UserService} 及 {@code ruoyi-system} 中任何既有方法/行为。</li>
|
||||
* <li>复用 {@code sys_user} 表,C 端账号 {@code user_type = app_user};不新增表、不新增字段。</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Kou
|
||||
* @date 2026-09-16
|
||||
*/
|
||||
public interface AppUserApiService {
|
||||
|
||||
/**
|
||||
* 按手机号查询 APP 用户(仅匹配 {@code user_type = app_user} 且未逻辑删除的账号)。
|
||||
*
|
||||
* @param phoneNumber 手机号
|
||||
* @return APP 用户信息(不含密码);不存在时返回 {@code null}
|
||||
*/
|
||||
AppUserDTO selectByPhone(String phoneNumber);
|
||||
|
||||
/**
|
||||
* 判断手机号是否已被注册为 APP 用户(仅匹配 {@code user_type = app_user})。
|
||||
*
|
||||
* @param phoneNumber 手机号
|
||||
* @return true 已注册;false 未注册
|
||||
*/
|
||||
boolean existsByPhone(String phoneNumber);
|
||||
|
||||
/**
|
||||
* 校验用户密码是否匹配。
|
||||
* <p>
|
||||
* 明文密码仅在本方法内部比对(BCrypt),比较结果以布尔值回传,
|
||||
* 密码散列与明文均<b>不</b>跨越模块边界。
|
||||
*
|
||||
* @param userId 用户主键
|
||||
* @param rawPassword 明文密码
|
||||
* @return true 匹配;false 不匹配或用户不存在
|
||||
*/
|
||||
boolean matchesPassword(Long userId, String rawPassword);
|
||||
|
||||
/**
|
||||
* 以手机号注册一个 APP 用户。
|
||||
* <p>
|
||||
* 账号用户名取手机号;密码在本方法内部散列后落库。
|
||||
*
|
||||
* @param phoneNumber 手机号
|
||||
* @param rawPassword 明文密码
|
||||
* @param nickName 昵称(为空时默认取手机号)
|
||||
* @return 新建用户主键
|
||||
*/
|
||||
Long registerByPhone(String phoneNumber, String rawPassword, String nickName);
|
||||
|
||||
/**
|
||||
* 按邮箱查询 APP 用户(仅匹配 {@code user_type = app_user} 且未逻辑删除的账号)。
|
||||
* <p>
|
||||
* 邮箱比对口径:调用方须先做「去空白 + 转小写」归一化(见
|
||||
* {@code org.dromara.device.email.AppEmailService#normalize}),本方法按传入值精确匹配。
|
||||
*
|
||||
* @param email 邮箱地址(已归一化)
|
||||
* @return APP 用户信息(不含密码);不存在时返回 {@code null}
|
||||
*/
|
||||
AppUserDTO selectByEmail(String email);
|
||||
|
||||
/**
|
||||
* 判断邮箱是否已被注册为 APP 用户(仅匹配 {@code user_type = app_user})。
|
||||
*
|
||||
* @param email 邮箱地址(已归一化)
|
||||
* @return true 已注册;false 未注册
|
||||
*/
|
||||
boolean existsByEmail(String email);
|
||||
|
||||
/**
|
||||
* 以邮箱注册一个 APP 用户(邮箱注册入口)。
|
||||
* <p>
|
||||
* <b>不写手机号</b>:本入口创建的账号 {@code phone_number} 为 {@code NULL}
|
||||
* (依赖 {@code script/sql/bm_patch.sql} 补丁 3 把该列默认值改为 NULL,
|
||||
* 否则 MyBatis-Plus 的 {@code insertStrategy: NOT_NULL} 会省略该列、落库为 {@code ''},
|
||||
* 导致第二个邮箱账号撞 {@code uk_sys_user_phone_number})。账号须在后续流程中绑定手机号。
|
||||
* <p>
|
||||
* 密码在本方法内部散列后落库。
|
||||
*
|
||||
* @param email 邮箱地址(已归一化)
|
||||
* @param rawPassword 明文密码
|
||||
* @param nickName 昵称(为空时默认取邮箱)
|
||||
* @return 新建用户主键
|
||||
*/
|
||||
Long registerByEmail(String email, String rawPassword, String nickName);
|
||||
|
||||
/**
|
||||
* 重置用户密码(忘记密码场景)。
|
||||
*
|
||||
* @param userId 用户主键
|
||||
* @param rawPassword 新明文密码
|
||||
* @return true 修改成功
|
||||
*/
|
||||
boolean resetPassword(Long userId, String rawPassword);
|
||||
|
||||
/**
|
||||
* 换绑手机号。
|
||||
*
|
||||
* @param userId 用户主键
|
||||
* @param newPhoneNumber 新手机号
|
||||
* @return true 修改成功
|
||||
*/
|
||||
boolean updatePhone(Long userId, String newPhoneNumber);
|
||||
|
||||
/**
|
||||
* 停用账号(注销前置:解绑设备后停用)。
|
||||
*
|
||||
* @param userId 用户主键
|
||||
* @return true 停用成功
|
||||
*/
|
||||
boolean disableUser(Long userId);
|
||||
|
||||
/**
|
||||
* 判断账号是否已绑定手机号。
|
||||
* <p>
|
||||
* 供设备域做「邮箱注册后仍须绑手机号」的服务端硬拦截使用
|
||||
* (未绑手机号的账号不允许绑定设备)。
|
||||
*
|
||||
* @param userId 用户主键
|
||||
* @return true 已绑定非空手机号;false 未绑定或用户不存在
|
||||
*/
|
||||
boolean isPhoneBound(Long userId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.dromara.system.api.domain;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* APP 用户传输对象。
|
||||
* <p>
|
||||
* 面向 APP 账号闭环的对外 DTO,<b>刻意不包含密码字段</b>,
|
||||
* 以确保密码散列不会跨越模块边界(见 {@link org.dromara.system.api.AppUserApiService})。
|
||||
*
|
||||
* @author Kou
|
||||
* @date 2026-09-16
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class AppUserDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户账号(APP 账号取手机号)
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 手机号码
|
||||
*/
|
||||
private String phoneNumber;
|
||||
|
||||
/**
|
||||
* 用户邮箱
|
||||
* <p>
|
||||
* 仅邮箱注册的账号非空;手机号注册的账号为 {@code null}。
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 用户类型(固定为 app_user)
|
||||
*/
|
||||
private String userType;
|
||||
|
||||
/**
|
||||
* 账号状态(0 正常 1 停用)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user