设备绑定支持

This commit is contained in:
zjf committed 2026-01-26 20:01:25 +08:00
1 parent b3c8c4b8a0
commit a1d9b4d3fc
15 files changed
+313 -17

No files matched your search

+24
View File
@@ -3820,6 +3820,30 @@ INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_t
INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (47, 104, 2, '1', '2025-01-04 10:40:33', '1', '2025-01-04 10:40:33', b'0', 1);
COMMIT;
-- ----------------------------
-- Table structure for system_user_device
-- ----------------------------
DROP TABLE IF EXISTS `system_user_device`;
CREATE TABLE `system_user_device` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增编号',
`user_id` bigint NOT NULL COMMENT '用户ID',
`device_id` bigint NOT NULL COMMENT '设备ID',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_user_device` (`user_id`, `device_id`, `tenant_id`, `deleted`) USING BTREE COMMENT '用户设备唯一索引'
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户和设备关联表';
-- ----------------------------
-- Records of system_user_device
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for system_users
-- ----------------------------
@@ -21,5 +21,12 @@ public interface DeviceApi {
List<DeviceRespDto> getDeviceByDeviceIds(List<String> deviceIds);
/**
* 根据设备ID列表(Long类型),获得设备信息列表
* @param ids 设备ID列表
* @return 设备信息列表
*/
List<DeviceRespDto> getDeviceListByIds(Collection<Long> ids);
int updateDeviceLastChannel(String deviceId, String cabinetChannel);
}
@@ -34,6 +34,11 @@ public class DeviceApiImpl implements DeviceApi {
return deviceService.getDeviceByDeviceIds(deviceIds);
}
@Override
public List<DeviceRespDto> getDeviceListByIds(Collection<Long> ids) {
return deviceService.getDeviceListByIds(ids);
}
@Override
public int updateDeviceLastChannel(String deviceId, String cabinetChannel) {
return deviceService.updateDeviceLastChannel(deviceId, cabinetChannel);
@@ -89,6 +89,22 @@ public class DeviceController {
return success(BeanUtils.toBean(pageResult, DeviceRespVO.class));
}
@GetMapping("/list-all-simple")
@Operation(summary = "获得贩卖机信息精简列表(用于下拉选择)")
@PreAuthorize("@ss.hasPermission('product:device:query')")
public CommonResult<List<DeviceSimpleRespVO>> getDeviceSimpleList() {
List<DeviceDO> list = deviceService.getDeviceSimpleList();
return success(BeanUtils.toBean(list, DeviceSimpleRespVO.class));
}
@GetMapping("/list-simple-by-user")
@Operation(summary = "获得贩卖机信息精简列表(根据用户绑定过滤,超级管理员返回全部)")
@PreAuthorize("@ss.hasPermission('product:device:query')")
public CommonResult<List<DeviceSimpleRespVO>> getDeviceSimpleListByUser() {
List<DeviceDO> list = deviceService.getDeviceSimpleListByUser();
return success(BeanUtils.toBean(list, DeviceSimpleRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出贩卖机信息 Excel")
@PreAuthorize("@ss.hasPermission('product:device:export')")
@@ -63,5 +63,29 @@ public interface DeviceService {
List<DeviceRespDto> getDeviceByDeviceIds(List<String> deviceIds);
/**
* 根据设备ID列表(Long类型),获得设备信息列表
*
* @param ids 设备ID列表
* @return 设备信息列表
*/
List<DeviceRespDto> getDeviceListByIds(Collection<Long> ids);
int updateDeviceLastChannel(String deviceId, String cabinetChannel);
/**
* 获得贩卖机信息精简列表(用于下拉选择)
* 根据当前登录用户的部门过滤设备
*
* @return 贩卖机信息精简列表
*/
List<DeviceDO> getDeviceSimpleList();
/**
* 获得贩卖机信息精简列表(用于下拉选择,根据用户绑定过滤)
* 超级管理员返回全部设备,普通用户返回绑定的设备
*
* @return 贩卖机信息精简列表
*/
List<DeviceDO> getDeviceSimpleListByUser();
}
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.product.service.device;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
import cn.iocoder.yudao.module.product.api.device.dto.DeviceRespDto;
@@ -34,10 +35,12 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.product.dal.mysql.device.DeviceMapper;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import org.springframework.web.client.RestTemplate;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserDeptId;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
/**
@@ -58,6 +61,11 @@ public class DeviceServiceImpl implements DeviceService {
@Resource
private FileApi fileApi;
@Resource
private cn.iocoder.yudao.module.system.api.user.AdminUserApi adminUserApi;
@Resource
private cn.iocoder.yudao.module.system.api.permission.PermissionApi permissionApi;
// TOKEN
@Value("${xr.cabinet.token}")
private String token;
@@ -245,11 +253,57 @@ public class DeviceServiceImpl implements DeviceService {
return BeanUtils.toBean(devices, DeviceRespDto.class);
}
@Override
public List<DeviceRespDto> getDeviceListByIds(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
List<DeviceDO> devices = deviceMapper.selectBatchIds(ids);
return BeanUtils.toBean(devices, DeviceRespDto.class);
}
@Override
public int updateDeviceLastChannel(String deviceId, String cabinetChannel) {
return deviceMapper.updateDeviceLastChannel(deviceId, cabinetChannel);
}
@Override
public List<DeviceDO> getDeviceSimpleList() {
// 查询所有设备,按ID倒序排列
return deviceMapper.selectList(new LambdaQueryWrapperX<DeviceDO>()
.orderByDesc(DeviceDO::getId));
}
@Override
public List<DeviceDO> getDeviceSimpleListByUser() {
// 获取当前登录用户的ID
Long userId = getLoginUserId();
if (userId == null) {
// 如果无法获取用户ID,返回空列表
return Collections.emptyList();
}
// 判断是否是超级管理员
boolean isSuperAdmin = permissionApi.hasAnyRoles(userId, "super_admin");
if (isSuperAdmin) {
// 超级管理员返回全部设备
return deviceMapper.selectList(new LambdaQueryWrapperX<DeviceDO>()
.orderByDesc(DeviceDO::getId));
}
// 普通用户:查询用户绑定的设备ID列表
List<Long> userDeviceIds = adminUserApi.getUserDeviceIds(userId);
if (CollUtil.isEmpty(userDeviceIds)) {
// 如果用户没有绑定设备,返回空列表
return Collections.emptyList();
}
// 根据绑定的设备ID查询设备列表
return deviceMapper.selectList(new LambdaQueryWrapperX<DeviceDO>()
.in(DeviceDO::getId, userDeviceIds)
.orderByDesc(DeviceDO::getId));
}
// 处理小程序二维码图片
private String disposeWechatQrcodeImage(byte[] imageByte) {
int baseMapWidth = 1920;
@@ -87,4 +87,12 @@ public interface AdminUserApi {
void validateUserList(Collection<Long> ids);
AdminUserRespDTO getUserByDeviceid(String deviceid);
/**
* 获取用户绑定的设备ID列表
*
* @param userId 用户ID
* @return 设备ID列表
*/
List<Long> getUserDeviceIds(Long userId);
}
@@ -25,7 +25,12 @@
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-infra-api</artifactId>
<artifactId>yudao-module-product-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-member-api</artifactId>
<version>${revision}</version>
</dependency>
@@ -89,4 +89,9 @@ public class AdminUserApiImpl implements AdminUserApi {
return BeanUtils.toBean(user, AdminUserRespDTO.class);
}
@Override
public List<Long> getUserDeviceIds(Long userId) {
return userService.getUserDeviceIds(userId);
}
}
@@ -14,6 +14,9 @@ import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.enums.common.SexEnum;
import cn.iocoder.yudao.module.system.service.dept.DeptService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.product.api.device.DeviceApi;
import cn.iocoder.yudao.module.product.api.device.dto.DeviceRespDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
@@ -27,9 +30,8 @@ import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -45,6 +47,10 @@ public class UserController {
private AdminUserService userService;
@Resource
private DeptService deptService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeviceApi deviceApi;
@PostMapping("/create")
@Operation(summary = "新增用户")
@@ -99,8 +105,44 @@ public class UserController {
// 拼接数据
Map<Long, DeptDO> deptMap = deptService.getDeptMap(
convertList(pageResult.getList(), AdminUserDO::getDeptId));
return success(new PageResult<>(UserConvert.INSTANCE.convertList(pageResult.getList(), deptMap),
pageResult.getTotal()));
List<UserRespVO> userList = UserConvert.INSTANCE.convertList(pageResult.getList(), deptMap);
// 查询用户绑定的设备并设置设备名称
List<Long> userIds = convertList(pageResult.getList(), AdminUserDO::getId);
Map<Long, List<Long>> userDeviceIdsMap = new HashMap<>();
for (Long userId : userIds) {
List<Long> deviceIds = adminUserApi.getUserDeviceIds(userId);
if (CollUtil.isNotEmpty(deviceIds)) {
userDeviceIdsMap.put(userId, deviceIds);
}
}
// 批量查询设备信息
Set<Long> allDeviceIds = userDeviceIdsMap.values().stream()
.flatMap(List::stream)
.collect(Collectors.toSet());
Map<Long, DeviceRespDto> deviceMap = new HashMap<>();
if (CollUtil.isNotEmpty(allDeviceIds)) {
List<DeviceRespDto> devices = deviceApi.getDeviceListByIds(allDeviceIds);
deviceMap = devices.stream()
.collect(Collectors.toMap(DeviceRespDto::getId, device -> device));
}
// 设置设备名称
for (UserRespVO userRespVO : userList) {
List<Long> deviceIds = userDeviceIdsMap.get(userRespVO.getId());
if (CollUtil.isNotEmpty(deviceIds)) {
String deviceNames = deviceIds.stream()
.map(deviceMap::get)
.filter(Objects::nonNull)
.map(DeviceRespDto::getName)
.filter(Objects::nonNull)
.collect(Collectors.joining(","));
userRespVO.setDeviceNames(deviceNames);
}
}
return success(new PageResult<>(userList, pageResult.getTotal()));
}
@GetMapping({"/list-all-simple", "/simple-list"})
@@ -169,4 +211,32 @@ public class UserController {
return success(userService.importUserList(list, updateSupport));
}
@PostMapping("/bind-devices")
@Operation(summary = "绑定用户设备")
@PreAuthorize("@ss.hasPermission('system:user:update')")
public CommonResult<Boolean> bindUserDevices(@Valid @RequestBody UserBindDeviceReqVO reqVO) {
userService.bindUserDevices(reqVO.getUserId(), reqVO.getDeviceIds());
return success(true);
}
@DeleteMapping("/unbind-devices")
@Operation(summary = "解绑用户设备")
@PreAuthorize("@ss.hasPermission('system:user:update')")
public CommonResult<Boolean> unbindUserDevices(@Valid @RequestBody UserBindDeviceReqVO reqVO) {
userService.unbindUserDevices(reqVO.getUserId(), reqVO.getDeviceIds());
return success(true);
}
@GetMapping("/get-device-ids")
@Operation(summary = "获取用户绑定的设备ID列表")
@Parameter(name = "userId", description = "用户ID", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:user:query')")
public CommonResult<UserDeviceRespVO> getUserDeviceIds(@RequestParam("userId") Long userId) {
List<Long> deviceIds = userService.getUserDeviceIds(userId);
UserDeviceRespVO respVO = new UserDeviceRespVO();
respVO.setUserId(userId);
respVO.setDeviceIds(deviceIds);
return success(respVO);
}
}
@@ -60,6 +60,10 @@ public class UserRespVO{
@Schema(description = "设备IMEI", example = "12345678910")
private String deviceId;
@Schema(description = "设备名称(多个用逗号分隔)", example = "贩卖机001,贩卖机002")
@ExcelProperty("设备名称")
private String deviceNames;
@Schema(description = "状态,参见 CommonStatusEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty(value = "帐号状态", converter = DictConvert.class)
@DictFormat(DictTypeConstants.COMMON_STATUS)
@@ -217,4 +217,28 @@ public interface AdminUserService {
boolean isPasswordMatch(String rawPassword, String encodedPassword);
AdminUserDO getUserByDeviceid(String deviceid);
/**
* 绑定用户设备
*
* @param userId 用户ID
* @param deviceIds 设备ID集合
*/
void bindUserDevices(Long userId, Collection<Long> deviceIds);
/**
* 解绑用户设备
*
* @param userId 用户ID
* @param deviceIds 设备ID集合
*/
void unbindUserDevices(Long userId, Collection<Long> deviceIds);
/**
* 获取用户绑定的设备ID列表
*
* @param userId 用户ID
* @return 设备ID列表
*/
List<Long> getUserDeviceIds(Long userId);
}
@@ -24,6 +24,8 @@ import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper;
import cn.iocoder.yudao.module.system.dal.mysql.user.UserDeviceMapper;
import cn.iocoder.yudao.module.system.dal.dataobject.user.UserDeviceDO;
import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper;
import cn.iocoder.yudao.module.system.service.dept.DeptService;
import cn.iocoder.yudao.module.system.service.dept.PostService;
@@ -79,6 +81,9 @@ public class AdminUserServiceImpl implements AdminUserService {
@Resource
private UserPostMapper userPostMapper;
@Resource
private UserDeviceMapper userDeviceMapper;
@Resource
private FileApi fileApi;
@Resource
@@ -252,8 +257,10 @@ public class AdminUserServiceImpl implements AdminUserService {
userMapper.deleteById(id);
// 2.2 删除用户关联数据
permissionService.processUserDeleted(id);
// 2.2 删除用户岗位
// 2.3 删除用户岗位
userPostMapper.deleteByUserId(id);
// 2.4 删除用户设备关联
userDeviceMapper.deleteByUserId(id);
// 3. 记录操作日志上下文
LogRecordContext.putVariable("user", user);
@@ -523,6 +530,49 @@ public class AdminUserServiceImpl implements AdminUserService {
return userMapper.getUserByDeviceid(deviceid);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void bindUserDevices(Long userId, Collection<Long> deviceIds) {
// 1. 校验用户存在
validateUserExists(userId);
// 2. 如果设备ID集合为空,直接返回
if (CollUtil.isEmpty(deviceIds)) {
return;
}
// 3. 查询已绑定的设备
List<UserDeviceDO> existingDevices = userDeviceMapper.selectListByUserId(userId);
Set<Long> existingDeviceIds = convertSet(existingDevices, UserDeviceDO::getDeviceId);
// 4. 计算需要新增的设备(去重)
Collection<Long> newDeviceIds = CollUtil.subtract(deviceIds, existingDeviceIds);
// 5. 批量插入新设备关联
if (CollUtil.isNotEmpty(newDeviceIds)) {
userDeviceMapper.insertBatch(convertList(newDeviceIds,
deviceId -> new UserDeviceDO().setUserId(userId).setDeviceId(deviceId)));
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void unbindUserDevices(Long userId, Collection<Long> deviceIds) {
// 1. 校验用户存在
validateUserExists(userId);
// 2. 如果设备ID集合为空,直接返回
if (CollUtil.isEmpty(deviceIds)) {
return;
}
// 3. 删除设备关联
userDeviceMapper.deleteByUserIdAndDeviceIds(userId, deviceIds);
}
@Override
public List<Long> getUserDeviceIds(Long userId) {
// 1. 校验用户存在
validateUserExists(userId);
// 2. 查询用户绑定的设备ID列表
List<UserDeviceDO> userDevices = userDeviceMapper.selectListByUserId(userId);
return convertList(userDevices, UserDeviceDO::getDeviceId);
}
/**
* 对密码进行加密
*
@@ -1,5 +1,5 @@
server:
port: 8089
port: 48089
--- #################### 数据库相关配置 ####################
spring:
@@ -49,14 +49,14 @@ spring:
# url: jdbc:mysql://123.157.250.162:37897/xr?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
# username: xr
# password: zd5ecNxeXHZxkWs8
url: jdbc:mysql://123.157.250.162:37897/xr_v2?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
username: xr_v2
password: GR6DMKkyXAxcZSe4
url: jdbc:mysql://127.0.0.1:3306/xr_v2?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
username: root
password: root
slave: # 模拟从库,可根据自己需要修改
lazy: true # 开启懒加载,保证启动速度
url: jdbc:mysql://123.157.250.162:37897/xr_v2?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true
username: xr_v2
password: GR6DMKkyXAxcZSe4
url: jdbc:mysql://127.0.0.1:3306/xr_v2?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
username: root
password: root
# tdengine: # IoT 数据库(需要 IoT 物联网再开启噢!)
# url: jdbc:TAOS-RS://127.0.0.1:6041/ruoyi_vue_pro
# driver-class-name: com.taosdata.jdbc.rs.RestfulDriver
@@ -70,7 +70,7 @@ spring:
host: 127.0.0.1 # 地址
port: 6379 # 端口
database: 10 # 数据库索引
# password: 123456 # 密码,建议生产环境开启
password: 123456 # 密码,建议生产环境开启
--- #################### 定时任务相关配置 ####################
@@ -3,8 +3,8 @@ spring:
name: yudao-server
profiles:
active: prod
# active: local
#active: prod
active: local
main:
allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。