Compare commits

...

3 Commits

Author SHA1 Message Date
f9a340c77c 查询设备 2025-06-21 13:48:37 +08:00
a02819261d 分配设备 2025-06-20 19:20:56 +08:00
b11d3ccd61 分配设备 2025-06-20 18:42:20 +08:00
14 changed files with 298 additions and 117 deletions

View File

@ -0,0 +1,32 @@
package com.fuyuanshen.utils.enums;
/**
* @author: 默苍璃
* @date: 2025-06-2019:08
*/
/**
* 表示 NanoId 生成时使用的长度配置
*/
public enum NanoIdLengthEnum {
/**
* 默认设备类型 ID 长度
*/
DEVICE_TYPE_ID(10),
/**
* 更长的唯一标识符,用于高并发场景
*/
HIGH_CONCURRENCY(15);
private final int length;
NanoIdLengthEnum(int length) {
this.length = length;
}
public int getLength() {
return length;
}
}

View File

@ -35,9 +35,17 @@ public class Device extends BaseEntity implements Serializable {
@ApiModelProperty(value = "客户号")
private Long customerId;
/**
* 当前所有者
* current_owner_id
*/
@ApiModelProperty(value = "当前所有者")
private Long currentOwnerId;
/**
* 原始所有者(创建者)
* original_owner_id
*/
@ApiModelProperty(value = "原始所有者(创建者)")
private Long originalOwnerId;

View File

@ -8,6 +8,8 @@ import lombok.Data;
import java.util.Date;
/**
* 设备分配记录表
*
* @TableName device_assignments
*/
@TableName(value = "device_assignments")
@ -35,6 +37,20 @@ public class DeviceAssignments {
*/
private Long toCustomerId;
/**
* 分配者
* assigner_name
*/
private Long assignerId;
private String assignerName;
/**
* 接收者
* assignee_name
*/
private Long assigneeId;
private String assigneeName;
/**
* 分配时间
*/
@ -47,48 +63,23 @@ public class DeviceAssignments {
*/
private Integer deviceTypeGranted;
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (getClass() != that.getClass()) {
return false;
}
DeviceAssignments other = (DeviceAssignments) that;
return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId())) && (this.getDeviceId() == null ? other.getDeviceId() == null : this.getDeviceId().equals(other.getDeviceId())) && (this.getFromCustomerId() == null ? other.getFromCustomerId() == null : this.getFromCustomerId().equals(other.getFromCustomerId())) && (this.getToCustomerId() == null ? other.getToCustomerId() == null : this.getToCustomerId().equals(other.getToCustomerId())) && (this.getAssignedAt() == null ? other.getAssignedAt() == null : this.getAssignedAt().equals(other.getAssignedAt())) && (this.getDeviceTypeGranted() == null ? other.getDeviceTypeGranted() == null : this.getDeviceTypeGranted().equals(other.getDeviceTypeGranted()));
}
/**
* 0 否
* 1 是
* 是否直接分配(用于父级显示)
*/
private Integer direct;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
result = prime * result + ((getDeviceId() == null) ? 0 : getDeviceId().hashCode());
result = prime * result + ((getFromCustomerId() == null) ? 0 : getFromCustomerId().hashCode());
result = prime * result + ((getToCustomerId() == null) ? 0 : getToCustomerId().hashCode());
result = prime * result + ((getAssignedAt() == null) ? 0 : getAssignedAt().hashCode());
result = prime * result + ((getDeviceTypeGranted() == null) ? 0 : getDeviceTypeGranted().hashCode());
return result;
}
/**
* 0 否
* 1 是
* 设备是否有效
*/
private Integer active;
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", id=").append(id);
sb.append(", deviceId=").append(deviceId);
sb.append(", fromCustomerId=").append(fromCustomerId);
sb.append(", toCustomerId=").append(toCustomerId);
sb.append(", assignedAt=").append(assignedAt);
sb.append(", deviceTypeGranted=").append(deviceTypeGranted);
sb.append("]");
return sb.toString();
}
/**
* 分配等级(用于失效)
*/
private String lever;
}

View File

@ -32,7 +32,8 @@ public class DeviceType extends BaseEntity implements Serializable {
private Long customerId;
@ApiModelProperty(value = "创建该类型的客户")
private String ownerCustomerId;
private Long ownerCustomerId;
/**
* 租户ID
*/

View File

@ -2,7 +2,9 @@ package com.fuyuanshen.modules.system.domain.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
@ -11,12 +13,15 @@ import java.util.List;
* @Date: 2025/5/28
**/
@Data
@Validated
public class CustomerVo {
@ApiModelProperty(value = "客户ID")
@NotNull(message = "客户ID不能为空")
private Long customerId;
@ApiModelProperty(value = "设备ID")
@NotNull(message = "设备ID不能为空")
private List<Long> deviceIds;
}

View File

@ -5,6 +5,7 @@ import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.alibaba.excel.converters.bytearray.ByteArrayImageConverter;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import com.fuyuanshen.modules.system.converter.IgnoreFailedImageConverter;
@ -43,6 +44,11 @@ public class DeviceExcelImportDTO {
@ColumnWidth(20)
private String deviceMac;
@ExcelProperty("设备IMEI")
@ColumnWidth(20)
@ApiModelProperty(value = "设备IMEI")
private String deviceImei;
@ExcelProperty("设备SN")
@ColumnWidth(20)
private String deviceSn;

View File

@ -25,6 +25,9 @@ public class DeviceQueryCriteria {
@ApiModelProperty(value = "设备MAC")
private String deviceMac;
@ApiModelProperty(value = "设备IMEI")
private String deviceImei;
@ApiModelProperty(value = "设备SN")
private String deviceSn;

View File

@ -0,0 +1,44 @@
package com.fuyuanshen.modules.system.enums;
/**
* 设备有效性状态枚举
*
* @author: 默苍璃
* @date: 2025-06-2113:26
*/
public enum DeviceActiveStatusEnum {
INACTIVE(0, "无效"), ACTIVE(1, "有效");
private final Integer code;
private final String description;
DeviceActiveStatusEnum(Integer code, String description) {
this.code = code;
this.description = description;
}
public Integer getCode() {
return code;
}
public String getDescription() {
return description;
}
/**
* 根据 code 获取描述
*
* @param code 状态码
* @return 描述信息
*/
public static String getDescriptionByCode(Integer code) {
for (DeviceActiveStatusEnum status : values()) {
if (status.getCode().equals(code)) {
return status.getDescription();
}
}
return null;
}
}

View File

@ -216,28 +216,20 @@ public class DeviceController {
try {
User currentUser = userMapper.findByUsername(SecurityUtils.getCurrentUsername());
DeviceImportParams params = DeviceImportParams.builder().ip(ip).deviceService(deviceService).tenantId(currentUser.getTenantId()).file(file).filePath(filePath).deviceMapper(deviceMapper).deviceTypeMapper(deviceTypeMapper).userId(currentUser.getId()).build();
// 创建监听器
UploadDeviceDataListener listener = new UploadDeviceDataListener(params);
// 读取Excel
EasyExcel.read(file.getInputStream(), DeviceExcelImportDTO.class, listener).sheet().doRead();
// 获取导入结果
result = listener.getImportResult();
// 设置响应消息
String message = String.format("成功导入 %d 条数据,失败 %d 条", result.getSuccessCount(), result.getFailureCount());
// 返回带有正确泛型的响应
return ResponseVO.<ImportResult>success(message, result);
} catch (Exception e) {
log.error("导入设备数据出错: {}", e.getMessage(), e);
// 在异常情况下,设置默认结果
String errorMessage = String.format("导入失败: %s。成功 %d 条,失败 %d 条", e.getMessage(), result.getSuccessCount(), result.getFailureCount());
// 使用新方法确保类型正确
return ResponseVO.<ImportResult>fail(errorMessage, result);
}

View File

@ -81,4 +81,5 @@ public class DeviceTypeController {
return ResponseVO.success(deviceTypeService.getById(id));
}
}

View File

@ -18,6 +18,7 @@ import com.fuyuanshen.modules.system.domain.dto.CustomerVo;
import com.fuyuanshen.modules.system.domain.dto.DeviceForm;
import com.fuyuanshen.modules.system.domain.dto.DeviceQueryCriteria;
import com.fuyuanshen.modules.system.enums.BindingStatusEnum;
import com.fuyuanshen.modules.system.enums.DeviceActiveStatusEnum;
import com.fuyuanshen.modules.system.enums.DeviceAuthorizationStatus;
import com.fuyuanshen.modules.system.enums.DeviceStatusEnum;
import com.fuyuanshen.modules.system.mapper.DeviceMapper;
@ -29,8 +30,10 @@ import com.fuyuanshen.modules.system.service.DeviceAssignmentsService;
import com.fuyuanshen.modules.system.service.DeviceService;
import com.fuyuanshen.modules.system.service.DeviceTypeGrantsService;
import com.fuyuanshen.modules.system.service.UserService;
import com.fuyuanshen.modules.utils.AdminCheckUtil;
import com.fuyuanshen.modules.utils.NanoId;
import com.fuyuanshen.utils.*;
import com.fuyuanshen.utils.enums.NanoIdLengthEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -55,6 +58,12 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> implements DeviceService {
/**
* 初级分配等级
*/
public static final int LEVEL_PRIMARY = 1;
@Value("${file.device.pic}")
private String filePath;
@Value("${file.device.ip}")
@ -97,7 +106,7 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
// 只能看到自己的创建的设备,以及被分配的设备。
if (onlineuser.getTenantId() != null && !onlineuser.getTenantId().equals(UserConstants.SUPER_ADMIN_ID)) {
// criteria.setTenantId(onlineuser.getTenantId());
criteria.setTenantId(onlineuser.getTenantId());
criteria.setCurrentOwnerId(onlineuser.getId());
}
@ -182,11 +191,24 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
device.setDeviceStatus(DeviceStatusEnum.NORMAL.getCode());
// SnowflakeGenerator snowflakeGenerator = new SnowflakeGenerator();
// device.setId(snowflakeGenerator.next());
device.setCurrentOwnerId(currentUser.getId());
device.setOriginalOwnerId(currentUser.getId());
deviceMapper.insert(device);
// 新增设备类型记录
DeviceAssignments assignments = new DeviceAssignments();
assignments.setDeviceId(device.getId());
assignments.setAssignedAt(new Date());
// 分配者
assignments.setAssignerId(currentUser.getId());
// 接收者
assignments.setAssigneeId(currentUser.getId());
assignments.setActive(DeviceActiveStatusEnum.ACTIVE.getCode());
String lever = currentUser.getId() + ":";
assignments.setLever(lever);
deviceAssignmentsService.save(assignments);
}
@ -242,13 +264,12 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
/**
* 分配客户
* 分配客户 历史记录版本
*
* @param customerVo
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void assignCustomer(CustomerVo customerVo) {
public void assignCustomer1(CustomerVo customerVo) {
// 防止管理员误操作
User currentUser = userService.findById(SecurityUtils.getCurrentUserId());
@ -268,7 +289,7 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
}
// 自定义16位id
Long generatedId = NanoId.generate(16);
Long generatedId = NanoId.generate(NanoIdLengthEnum.HIGH_CONCURRENCY.getLength());
// -- 记录分配历史
DeviceAssignments deviceAssignments = new DeviceAssignments();
@ -296,7 +317,7 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
// -- 更新设备所有者
device.setCurrentOwnerId(customerVo.getCustomerId());
devices.add( device);
devices.add(device);
});
deviceAssignmentsService.saveBatch(assignments);
@ -307,76 +328,112 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
/**
* 分配客户代码复制
* 分配客户
*
* @param customerVo
*/
public void assignCustomer1(CustomerVo customerVo) {
@Override
@Transactional(rollbackFor = Exception.class)
public void assignCustomer(CustomerVo customerVo) {
// 防止管理员误操作
// 获取当前登录用户
User currentUser = userService.findById(SecurityUtils.getCurrentUserId());
if (currentUser.getTenantId().equals(UserConstants.SUPER_ADMIN_ID)) {
throw new BadRequestException(ExceptionMessages.ADMIN_OPERATION_NOT_ALLOWED);
}
// 防止管理员误操作
AdminCheckUtil.checkIfSuperAdmin(currentUser);
// 获取分配用户信息
User user = userService.findById(customerVo.getCustomerId());
// 获取分配设备信息
List<Device> devices = deviceMapper.selectBatchIds(customerVo.getDeviceIds());
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
// Timestamp timestamp = new Timestamp(System.currentTimeMillis());
for (Device device : devices) {
device.setCustomerId(user.getId());
device.setCustomerName(user.getNickName());
device.setUpdateTime(timestamp);
// device.setUpdateTime(timestamp);
}
this.updateBatchById(devices);
// 批量更新设备状态
List<DeviceTypeGrants> deviceTypeGrants = new ArrayList<>();
for (Device device : devices) {
// 获取当前用户的所有祖先用户
List<User> ancestorsById = userMapper.findAncestorsById(currentUser.getId());
Set<Long> excludedTenantIds = new HashSet<>();
if (CollectionUtil.isNotEmpty(ancestorsById)) {
// 提取所有需要排除的 tenant_id
excludedTenantIds = ancestorsById.stream().map(User::getTenantId).distinct().collect(Collectors.toSet());
}
excludedTenantIds.add(currentUser.getTenantId());
// 构建查询条件device_mac 相同,并且 tenant_id 不在 excludedTenantIds 列表中
Wrapper<Device> wrapper = new QueryWrapper<Device>().eq("device_mac", device.getDeviceMac()).notIn("tenant_id", excludedTenantIds);
// 构建要更新的数据
Device updateDevice = new Device();
updateDevice.setDeviceStatus(0);
updateDevice.setUpdateTime(new Timestamp(System.currentTimeMillis()));
// 如果设备已分配给需要分配的客户,则跳过
if (device.getCustomerId() != null && device.getCustomerId().equals(customerVo.getCustomerId())) {
continue;
}
// // 获取当前用户的所有祖先用户
// List<User> ancestorsById = userMapper.findAncestorsById(currentUser.getId());
// Set<Long> excludedTenantIds = new HashSet<>();
// if (CollectionUtil.isNotEmpty(ancestorsById)) {
// // 提取所有需要排除的 tenant_id
// excludedTenantIds = ancestorsById.stream().map(User::getTenantId).distinct().collect(Collectors.toSet());
// }
// excludedTenantIds.add(currentUser.getTenantId());
// // 构建查询条件device_mac 相同,并且 tenant_id 不在 excludedTenantIds 列表中
// Wrapper<Device> wrapper = new QueryWrapper<Device>().eq("device_mac", device.getDeviceMac()).notIn("tenant_id", excludedTenantIds);
//
// // 构建要更新的数据
// Device updateDevice = new Device();
// updateDevice.setDeviceStatus(0);
// updateDevice.setUpdateTime(new Timestamp(System.currentTimeMillis()));
// 根据条件批量更新
deviceMapper.update(updateDevice, wrapper);
// deviceMapper.update(updateDevice, wrapper);
// 创建并保存设备类型授权记录
createAndSaveDeviceTypeGrants(device, currentUser, customerVo, deviceTypeGrants);
}
deviceTypeGrantsService.saveBatch(deviceTypeGrants);
// 批量分配
Set<DeviceType> deviceTypes = new HashSet<>();
for (Device device : devices) {
device.setId(null);
device.setCustomerName(null);
device.setCustomerId(null);
device.setTenantId(user.getTenantId());
device.setCreateTime(timestamp);
device.setCreateBy(currentUser.getUsername());
device.setUpdateTime(timestamp);
//
// // 批量分配
// Set<DeviceType> deviceTypes = new HashSet<>();
// for (Device device : devices) {
// device.setId(null);
// device.setCustomerName(null);
// device.setCustomerId(null);
// device.setTenantId(user.getTenantId());
// device.setCreateTime(timestamp);
// device.setCreateBy(currentUser.getUsername());
// device.setUpdateTime(timestamp);
//
// // 查询设备类型
// DeviceType deviceType = deviceTypeMapper.selectById(device.getDeviceType());
// // SnowflakeGenerator snowflakeGenerator = new SnowflakeGenerator();
// Long id = NanoId.generate(NanoIdLengthEnum.HIGH_CONCURRENCY.getLength());
// deviceType.setId(id);
// deviceType.setCreateTime(timestamp);
// deviceType.setCreateBy(currentUser.getUsername());
// deviceType.setCustomerId(customerVo.getCustomerId());
// deviceTypes.add(deviceType);
//
// device.setDeviceType(deviceType.getId());
// }
// this.saveBatch(devices);
// deviceTypeService.saveBatch(deviceTypes);
// 查询设备类型
DeviceType deviceType = deviceTypeMapper.selectById(device.getDeviceType());
// SnowflakeGenerator snowflakeGenerator = new SnowflakeGenerator();
Long id = NanoId.generate(16);
deviceType.setId(id);
deviceType.setCreateTime(timestamp);
deviceType.setCreateBy(currentUser.getUsername());
deviceType.setCustomerId(customerVo.getCustomerId());
deviceTypes.add(deviceType);
}
device.setDeviceType(deviceType.getId());
}
this.saveBatch(devices);
deviceTypeService.saveBatch(deviceTypes);
/**
* 创建并保存设备类型授权记录
*
* @param device 当前设备对象
* @param currentUser 当前登录用户
* @param customerVo 客户信息
* @param deviceTypeGrants 授权记录集合
*/
private void createAndSaveDeviceTypeGrants(Device device, User currentUser, CustomerVo customerVo, List<DeviceTypeGrants> deviceTypeGrants) {
Long generatedId = NanoId.generate(NanoIdLengthEnum.HIGH_CONCURRENCY.getLength());
DeviceTypeGrants deviceTypeGrant = new DeviceTypeGrants();
deviceTypeGrant.setGrantedAt(new Date());
deviceTypeGrant.setDeviceTypeId(device.getDeviceType());
deviceTypeGrant.setAssignmentId(generatedId);
deviceTypeGrant.setCustomerId(customerVo.getCustomerId());
deviceTypeGrant.setGrantorCustomerId(currentUser.getId());
deviceTypeGrants.add(deviceTypeGrant);
}

View File

@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fuyuanshen.modules.system.domain.DeviceTypeGrants;
import com.fuyuanshen.modules.system.mapper.DeviceTypeGrantsMapper;
import com.fuyuanshen.modules.utils.NanoId;
import com.fuyuanshen.utils.enums.NanoIdLengthEnum;
import lombok.RequiredArgsConstructor;
import com.fuyuanshen.modules.security.service.UserCacheManager;
import com.fuyuanshen.modules.security.service.dto.JwtUserDto;
@ -125,9 +126,10 @@ public class DeviceTypeServiceImpl extends ServiceImpl<DeviceTypeMapper, DeviceT
@Override
@Transactional(rollbackFor = Exception.class)
public void create(DeviceType resources) {
Long deviceTypeId = NanoId.generate(16);
Long deviceTypeId = NanoId.generate(NanoIdLengthEnum.HIGH_CONCURRENCY.getLength());
resources.setId(deviceTypeId);
resources.setCustomerId(SecurityUtils.getCurrentUserId());
resources.setOwnerCustomerId(SecurityUtils.getCurrentUserId());
deviceTypeMapper.insert(resources);
// 自动授权给自己
@ -137,7 +139,6 @@ public class DeviceTypeServiceImpl extends ServiceImpl<DeviceTypeMapper, DeviceT
deviceTypeGrants.setGrantorCustomerId(SecurityUtils.getCurrentUserId());
deviceTypeGrants.setGrantedAt(new Date());
deviceTypeGrantsMapper.insert(deviceTypeGrants);
}

View File

@ -0,0 +1,40 @@
package com.fuyuanshen.modules.utils;
import com.fuyuanshen.constants.ExceptionMessages;
import com.fuyuanshen.exception.BadRequestException;
import com.fuyuanshen.modules.system.constant.UserConstants;
import com.fuyuanshen.modules.system.domain.User;
import com.fuyuanshen.modules.system.service.UserService;
import com.fuyuanshen.utils.SecurityUtils;
/**
* @author: 默苍璃
* @date: 2025-06-2110:35
*/
public class AdminCheckUtil {
/**
* 检查当前用户是否为超级管理员,如果是则抛出异常,防止误操作
*
* @param userService UserService 用于获取当前用户信息
*/
public static void preventAdminMisoperation(UserService userService) {
User currentUser = userService.findById(SecurityUtils.getCurrentUserId());
if (currentUser.getTenantId().equals(UserConstants.SUPER_ADMIN_ID)) {
throw new BadRequestException(ExceptionMessages.ADMIN_OPERATION_NOT_ALLOWED);
}
}
/**
* 检查用户是否是超级管理员,如果是则抛出异常
*
* @param user 当前用户对象
*/
public static void checkIfSuperAdmin(User user) {
if (user.getTenantId().equals(UserConstants.SUPER_ADMIN_ID)) {
throw new BadRequestException(ExceptionMessages.ADMIN_OPERATION_NOT_ALLOWED);
}
}
}

View File

@ -39,11 +39,12 @@
<!-- 分页查询设备 -->
<select id="findAll" resultType="com.fuyuanshen.modules.system.domain.Device">
select
<include refid="device_Column_List"/>,
t.type_name as typeName
d.id, d.customer_id, d.customer_name, d.device_name,
d.device_pic, d.device_mac, d.device_sn, d.create_by, d.update_by,d.device_imei,
d.create_time, d.update_time, d.device_type, d.remark, d.device_status, d.binding_status,t.type_name
from device d
left join device_type t on d.device_type = t.id
left join device_assignments da on d.current_owner_id = da.from_customer_id
LEFT JOIN device_type t ON d.device_type = t.id
LEFT JOIN device_assignments da ON da.device_id = d.id
<where>
<!-- 时间范围等其他条件保持原样 -->
<if test="criteria.deviceName != null and criteria.deviceName.trim() != ''">
@ -52,8 +53,8 @@
<if test="criteria.deviceMac != null">
and d.device_mac = #{criteria.deviceMac}
</if>
<if test="criteria.deviceSn != null">
and d.device_sn = #{criteria.deviceSn}
<if test="criteria.deviceImei != null">
and d.device_imei = #{criteria.deviceImei}
</if>
<if test="criteria.deviceType != null">
and d.device_type = #{criteria.deviceType}
@ -65,9 +66,10 @@
and d.create_time between #{criteria.createTime[0]} and #{criteria.createTime[1]}
</if>
<if test="criteria.currentOwnerId != null">
AND (current_owner_id = #{criteria.currentOwnerId} OR da.from_customer_id = #{criteria.currentOwnerId})
<if test="criteria.tenantId != null">
AND tenant_id = #{criteria.tenantId}
</if>
AND da.assignee_id = #{criteria.currentOwnerId}
<!-- 下面这两个条件只需满足一个 -->
@ -80,8 +82,6 @@
<!-- <if test="criteria.customerId == null"> -->
<!-- AND tenant_id = #{criteria.tenantId} -->
<!-- </if> -->
</where>
order by d.id desc