设备列表 视频流服务器

This commit is contained in:
yuhaiming
2026-09-17 17:09:49 +08:00
parent 1cfb071dfb
commit 7bdc5c846d
40 changed files with 1283 additions and 78 deletions

View File

@@ -2,19 +2,25 @@ package org.dromara.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.PageResult;
import org.dromara.common.core.enums.UserType;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.query.QueryBuilder;
import org.dromara.common.mybatis.utils.IdGeneratorUtil;
import org.dromara.system.api.AppUserApiService;
import org.dromara.system.api.domain.AppUserDTO;
import org.dromara.system.api.domain.AppUserQueryDTO;
import org.dromara.system.domain.SysUser;
import org.dromara.system.mapper.SysUserMapper;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
/**
* APP 用户领域服务({@link AppUserApiService})实现。
@@ -240,6 +246,13 @@ public class AppUserApiServiceImpl implements AppUserApiService {
/**
* 重置用户密码(忘记密码场景);新密码在本方法内部散列后落库。
* <p>
* <b>限定 {@code user_type = app_user}</b>(2026-09-17 加固):本方法原先只按 {@code userId} 更新,
* 若被后台管理端以任意主键调用,即可重置<b>后台账号</b>密码——属越权写入。
* 现补上类型条件,使「重置密码」这一高危动作的爆炸半径限定在 APP 用户内。
* <p>
* 对既有调用方<b>无行为影响</b>:三处调用(手机号找回 / 邮箱找回 / APP 内改密)的 {@code userId}
* 均取自 {@code selectByPhone} / {@code selectByEmail} / 登录态,本就恒为 app_user。
*
* @param userId 用户主键
* @param rawPassword 新明文密码
@@ -253,6 +266,7 @@ public class AppUserApiServiceImpl implements AppUserApiService {
return userMapper.lambda()
.set(SysUser::getPassword, BCrypt.hashpw(rawPassword))
.eq(SysUser::getUserId, userId)
.eq(SysUser::getUserType, UserType.APP_USER.getUserType())
.updateCount() > 0;
}
@@ -312,4 +326,107 @@ public class AppUserApiServiceImpl implements AppUserApiService {
return user != null && StringUtils.isNotBlank(user.getPhoneNumber());
}
// ---------------- 后台管理端(APP 用户管理) ----------------
/**
* 分页查询 APP 用户列表(后台「APP 用户管理」用)。
* <p>
* <b>强制 {@code user_type = app_user}</b>:该条件由本方法固定附加,
* {@link AppUserQueryDTO} 中也没有对应字段可覆盖,因此结果集恒不含后台账号。
* <p>
* <b>为什么刻意不加 {@code @DataPermission}</b>:APP 账号注册时不写 {@code dept_id}
* (见 {@link #registerByPhone}/{@link #registerByEmail}),该列恒为 {@code NULL};
* 若叠加框架按部门维度的数据权限过滤,会把全部 APP 用户过滤成空集。
* APP 用户本就游离于后台组织架构之外,故此处按「业务归属正确」优先。
* <p>
* <b>排序</b>:按注册时间倒序(新注册的排前面),与后台「看最新」的使用习惯一致。
*
* @param query 查询条件(关键词 / 状态 / 注册时间区间 / 分页)
* @return APP 用户分页结果(不含密码)
*/
@Override
public PageResult<AppUserDTO> selectAppUserPage(AppUserQueryDTO query) {
AppUserQueryDTO condition = query == null ? new AppUserQueryDTO() : query;
int pageNum = (condition.getPageNum() == null || condition.getPageNum() < 1) ? 1 : condition.getPageNum();
int pageSize = (condition.getPageSize() == null || condition.getPageSize() < 1) ? 10 : condition.getPageSize();
String keyword = condition.getKeyword();
LambdaQueryWrapper<SysUser> wrapper = QueryBuilder.lambda(SysUser.class)
.eq(SysUser::getUserType, UserType.APP_USER.getUserType())
.and(StringUtils.isNotBlank(keyword), w -> w
.like(SysUser::getUserName, keyword)
.or().like(SysUser::getNickName, keyword)
.or().like(SysUser::getPhoneNumber, keyword)
.or().like(SysUser::getEmail, keyword))
.eqIfText(SysUser::getStatus, condition.getStatus())
.betweenParams(SysUser::getCreateTime, condition.getParams(), "beginTime", "endTime")
.orderByDesc(SysUser::getCreateTime)
.build();
Page<SysUser> page = userMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
List<AppUserDTO> rows = page.getRecords().stream()
.map(user -> BeanUtil.copyProperties(user, AppUserDTO.class))
.toList();
return PageResult.build(rows, page.getTotal());
}
/**
* 按主键查询 APP 用户详情(后台「APP 用户管理」用)。
* <p>
* <b>强制 {@code user_type = app_user}</b>:传入后台账号主键时返回 {@code null},
* 使管理端无法借该接口读到系统账号信息(越界读取防护)。
*
* @param userId 用户主键
* @return APP 用户信息(不含密码);不存在或非 app_user 时返回 {@code null}
*/
@Override
public AppUserDTO selectAppUserById(Long userId) {
if (userId == null) {
return null;
}
SysUser user = userMapper.lambda()
.eq(SysUser::getUserId, userId)
.eq(SysUser::getUserType, UserType.APP_USER.getUserType())
.one();
return user == null ? null : BeanUtil.copyProperties(user, AppUserDTO.class);
}
/**
* 修改 APP 用户账号状态(停用 / 启用)。
* <p>
* <b>为什么同时限定 {@code user_type}</b>:本方法来自后台管理端(有登录态),
* 若不限定类型,构造任意 {@code userId} 即可停用后台账号——属于典型的越权写入。
* 加上该条件后,「非 APP 账号」的 update 影响行数为 0,天然被拒。
* <p>
* 返回 {@code true} 表示「目标状态已生效」,包含「原本就是该状态」的情形
* ({@code updateCount} 为 0 但语义上无需变更),便于前端幂等重试。
*
* @param userId 用户主键
* @param status 目标状态({@code 0} 正常 / {@code 1} 停用)
* @return true 目标状态已生效;false 目标用户不存在或非 APP 账号
*/
@Override
public boolean updateStatus(Long userId, String status) {
if (userId == null || StringUtils.isBlank(status)) {
return false;
}
// 先确认目标确为 APP 账号,避免「影响行数 0」被误判为「已是目标状态」
SysUser exist = userMapper.lambda()
.select(SysUser::getUserId, SysUser::getStatus)
.eq(SysUser::getUserId, userId)
.eq(SysUser::getUserType, UserType.APP_USER.getUserType())
.one();
if (exist == null) {
return false;
}
if (Objects.equals(exist.getStatus(), status)) {
return true;
}
return userMapper.lambda()
.set(SysUser::getStatus, status)
.eq(SysUser::getUserId, userId)
.eq(SysUser::getUserType, UserType.APP_USER.getUserType())
.updateCount() > 0;
}
}