修改字典值逻辑.

This commit is contained in:
lijiahang
2023-10-17 11:33:48 +08:00
parent b246515558
commit f13395f351
18 changed files with 261 additions and 44 deletions

View File

@@ -51,6 +51,8 @@ public interface ErrorMessage {
String MAX_LOGIN_FAILED = "登陆失败次数已上限";
String HISTORY_ABSENT = "历史值不存在";
String USER_ABSENT = "用户不存在";
String HOST_ABSENT = "主机不存在";

View File

@@ -13,6 +13,8 @@ public interface OperatorLogKeys {
String ID_LIST = "idList";
String KEY = "key";
String CODE = "code";
String NAME = "name";

View File

@@ -32,6 +32,16 @@ public interface HistoryValueApi {
*/
HistoryValueDTO getHistoryValueById(Long id);
/**
* 查询历史归档
*
* @param id id
* @param relId relId
* @param type type
* @return row
*/
HistoryValueDTO getHistoryValueByRelId(Long id, Long relId, HistoryValueTypeEnum type);
/**
* 删除历史归档
*

View File

@@ -56,6 +56,19 @@ public class HistoryValueApiImpl implements HistoryValueApi {
return HistoryValueProviderConvert.MAPPER.to(record);
}
@Override
public HistoryValueDTO getHistoryValueByRelId(Long id, Long relId, HistoryValueTypeEnum type) {
log.info("HistoryValueApi.getHistoryValueByRelId id: {}, relId: {}, type: {}", id, relId, type);
Valid.allNotNull(id, relId, type);
// 修改
HistoryValueDO record = historyValueService.getHistoryByRelId(id, relId, type.name());
if (record == null) {
return null;
}
// 转换
return HistoryValueProviderConvert.MAPPER.to(record);
}
@Override
public Integer deleteByRelId(HistoryValueTypeEnum type, Long relId) {
return historyValueService.deleteByRelId(type.name(), relId);

View File

@@ -9,6 +9,7 @@ import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.infra.define.operator.DictValueOperatorType;
import com.orion.ops.module.infra.entity.request.dict.DictValueCreateRequest;
import com.orion.ops.module.infra.entity.request.dict.DictValueQueryRequest;
import com.orion.ops.module.infra.entity.request.dict.DictValueRollbackRequest;
import com.orion.ops.module.infra.entity.request.dict.DictValueUpdateRequest;
import com.orion.ops.module.infra.entity.vo.DictValueVO;
import com.orion.ops.module.infra.service.DictValueService;
@@ -58,6 +59,14 @@ public class DictValueController {
return dictValueService.updateDictValueById(request);
}
@OperatorLog(DictValueOperatorType.UPDATE)
@PutMapping("/rollback")
@Operation(summary = "回滚字典配置值")
@PreAuthorize("@ss.hasPermission('infra:dict-value:update')")
public Integer rollbackDictValue(@Validated @RequestBody DictValueRollbackRequest request) {
return dictValueService.rollbackDictValueById(request);
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/list")
@Operation(summary = "查询字典配置值")

View File

@@ -25,10 +25,9 @@ public class DictKeyOperatorType extends InitializingOperatorTypes {
@Override
public OperatorType[] types() {
return new OperatorType[]{
// todo 添加参数
new OperatorType(L, CREATE, "创建字典配置项"),
new OperatorType(M, UPDATE, "更新字典配置项"),
new OperatorType(H, DELETE, "删除字典配置项"),
new OperatorType(L, CREATE, "创建字典配置项 <sb>${key}</sb>"),
new OperatorType(M, UPDATE, "更新字典配置项 <sb>${key}</sb>"),
new OperatorType(H, DELETE, "删除字典配置项 <sb>${key}</sb>"),
};
}

View File

@@ -20,18 +20,14 @@ public class DictValueOperatorType extends InitializingOperatorTypes {
public static final String UPDATE = "dict-value:update";
// todo 实现
public static final String ROLLBACK = "dict-value:rollback";
public static final String DELETE = "dict-value:delete";
@Override
public OperatorType[] types() {
return new OperatorType[]{
// todo 添加参数
new OperatorType(L, CREATE, "创建字典配置值"),
new OperatorType(M, UPDATE, "更新字典配置值"),
new OperatorType(H, DELETE, "删除字典配置值"),
new OperatorType(L, CREATE, "创建字典配置值 <sb>${key}</sb> <sb>${label}=${value}</sb>"),
new OperatorType(M, UPDATE, "更新字典配置值 <sb>${key}</sb> <sb>${label}=${value}</sb>"),
new OperatorType(H, DELETE, "删除字典配置值 <sb>${value}</sb>"),
};
}

View File

@@ -7,6 +7,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.io.Serializable;
@@ -26,6 +27,7 @@ public class DictKeyCreateRequest implements Serializable {
@NotBlank
@Size(max = 32)
@Pattern(regexp = "^[a-zA-Z0-9]{4,32}$")
@Schema(description = "配置项")
private String key;

View File

@@ -8,6 +8,7 @@ import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.io.Serializable;
@@ -31,6 +32,7 @@ public class DictKeyUpdateRequest implements Serializable {
@NotBlank
@Size(max = 32)
@Pattern(regexp = "^[a-zA-Z0-9]{4,32}$")
@Schema(description = "配置项")
private String key;

View File

@@ -8,6 +8,7 @@ import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.io.Serializable;
@@ -31,6 +32,7 @@ public class DictValueCreateRequest implements Serializable {
@NotBlank
@Size(max = 32)
@Pattern(regexp = "^[a-zA-Z0-9]{4,32}$")
@Schema(description = "配置名称")
private String label;

View File

@@ -0,0 +1,34 @@
package com.orion.ops.module.infra.entity.request.dict;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* 字典配置值 回滚请求对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-10-16 16:33
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "DictValueRollbackRequest", description = "字典配置值 回滚请求对象")
public class DictValueRollbackRequest implements Serializable {
@NotNull
@Schema(description = "id")
private Long id;
@NotNull
@Schema(description = "历史值id")
private Long valueId;
}

View File

@@ -8,6 +8,7 @@ import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.io.Serializable;
@@ -35,6 +36,7 @@ public class DictValueUpdateRequest implements Serializable {
@NotBlank
@Size(max = 32)
@Pattern(regexp = "^[a-zA-Z0-9]{4,32}$")
@Schema(description = "配置名称")
private String label;

View File

@@ -0,0 +1,49 @@
package com.orion.ops.module.infra.enums;
import lombok.Getter;
/**
* 字典值类型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/17 11:18
*/
@Getter
public enum DictValueTypeEnum {
/**
* 字符串
*/
STRING,
/**
* 数值
*/
NUMBER,
/**
* 布尔值
*/
BOOLEAN,
/**
* 颜色
*/
COLOR,
;
public static DictValueTypeEnum of(String type) {
if (type == null) {
return STRING;
}
for (DictValueTypeEnum value : values()) {
if (value.name().equals(type)) {
return value;
}
}
return STRING;
}
}

View File

@@ -3,6 +3,7 @@ package com.orion.ops.module.infra.service;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.module.infra.entity.request.dict.DictValueCreateRequest;
import com.orion.ops.module.infra.entity.request.dict.DictValueQueryRequest;
import com.orion.ops.module.infra.entity.request.dict.DictValueRollbackRequest;
import com.orion.ops.module.infra.entity.request.dict.DictValueUpdateRequest;
import com.orion.ops.module.infra.entity.vo.DictValueVO;
@@ -33,6 +34,14 @@ public interface DictValueService {
*/
Integer updateDictValueById(DictValueUpdateRequest request);
/**
* 更新字典配置值
*
* @param request request
* @return effect
*/
Integer rollbackDictValueById(DictValueRollbackRequest request);
/**
* 查询全部字典配置值
*

View File

@@ -41,6 +41,16 @@ public interface HistoryValueService {
*/
HistoryValueDO getHistoryById(Long id);
/**
* 通过 id 查询
*
* @param id id
* @param relId relId
* @param type type
* @return value
*/
HistoryValueDO getHistoryByRelId(Long id, Long relId, String type);
/**
* 删除历史归档
*

View File

@@ -3,6 +3,7 @@ package com.orion.ops.module.infra.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.lang.utils.Objects1;
import com.orion.ops.framework.biz.operator.log.core.uitls.OperatorLogs;
import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.common.constant.ErrorMessage;
import com.orion.ops.framework.common.utils.Valid;
@@ -15,6 +16,7 @@ import com.orion.ops.module.infra.entity.dto.DictKeyCacheDTO;
import com.orion.ops.module.infra.entity.request.dict.DictKeyCreateRequest;
import com.orion.ops.module.infra.entity.request.dict.DictKeyUpdateRequest;
import com.orion.ops.module.infra.entity.vo.DictKeyVO;
import com.orion.ops.module.infra.enums.DictValueTypeEnum;
import com.orion.ops.module.infra.service.DictKeyService;
import com.orion.ops.module.infra.service.DictValueService;
import lombok.extern.slf4j.Slf4j;
@@ -45,7 +47,8 @@ public class DictKeyServiceImpl implements DictKeyService {
@Override
public Long createDictKey(DictKeyCreateRequest request) {
log.info("DictKeyService-createDictKey request: {}" , JSON.toJSONString(request));
log.info("DictKeyService-createDictKey request: {}", JSON.toJSONString(request));
Valid.valid(DictValueTypeEnum::of, request.getValueType());
// 转换
DictKeyDO record = DictKeyConvert.MAPPER.to(request);
// 查询数据是否冲突
@@ -53,7 +56,7 @@ public class DictKeyServiceImpl implements DictKeyService {
// 插入
int effect = dictKeyDAO.insert(record);
Long id = record.getId();
log.info("DictKeyService-createDictKey id: {}, effect: {}" , id, effect);
log.info("DictKeyService-createDictKey id: {}, effect: {}", id, effect);
// 删除缓存
RedisMaps.delete(DictCacheKeyDefine.DICT_KEY);
return id;
@@ -63,10 +66,11 @@ public class DictKeyServiceImpl implements DictKeyService {
@Transactional(rollbackFor = Exception.class)
public Integer updateDictKeyById(DictKeyUpdateRequest request) {
Long id = Valid.notNull(request.getId(), ErrorMessage.ID_MISSING);
log.info("DictKeyService-updateDictKeyById id: {}, request: {}" , id, JSON.toJSONString(request));
log.info("DictKeyService-updateDictKeyById id: {}, request: {}", id, JSON.toJSONString(request));
Valid.valid(DictValueTypeEnum::of, request.getValueType());
// 查询
DictKeyDO record = dictKeyDAO.selectById(id);
Valid.notNull(record, ErrorMessage.DATA_ABSENT);
Valid.notNull(record, ErrorMessage.CONFIG_ABSENT);
// 转换
DictKeyDO updateRecord = DictKeyConvert.MAPPER.to(request);
// 查询数据是否冲突
@@ -79,7 +83,7 @@ public class DictKeyServiceImpl implements DictKeyService {
}
// 删除缓存
RedisMaps.delete(DictCacheKeyDefine.DICT_KEY);
log.info("DictKeyService-updateDictKeyById effect: {}" , effect);
log.info("DictKeyService-updateDictKeyById effect: {}", effect);
return effect;
}
@@ -110,27 +114,38 @@ public class DictKeyServiceImpl implements DictKeyService {
@Override
public Integer deleteDictKeyById(Long id) {
log.info("DictKeyService-deleteDictKeyById id: {}" , id);
log.info("DictKeyService-deleteDictKeyById id: {}", id);
// 检查数据是否存在
DictKeyDO record = dictKeyDAO.selectById(id);
Valid.notNull(record, ErrorMessage.DATA_ABSENT);
Valid.notNull(record, ErrorMessage.CONFIG_ABSENT);
OperatorLogs.add(OperatorLogs.KEY, record.getKey());
// 删除配置项
int effect = dictKeyDAO.deleteById(id);
// 删除配置值
dictValueService.deleteDictValueByKeyId(id);
// 删除缓存
RedisMaps.delete(DictCacheKeyDefine.DICT_KEY, id);
log.info("DictKeyService-deleteDictKeyById id: {}, effect: {}" , id, effect);
log.info("DictKeyService-deleteDictKeyById id: {}, effect: {}", id, effect);
return effect;
}
@Override
public Integer deleteDictKeyByIdList(List<Long> idList) {
log.info("DictKeyService-deleteDictKeyByIdList idList: {}" , idList);
log.info("DictKeyService-deleteDictKeyByIdList idList: {}", idList);
// 检查数据是否存在
List<DictKeyDO> dictKeys = dictKeyDAO.selectBatchIds(idList);
if (dictKeys.isEmpty()) {
return 0;
}
String keys = dictKeys.stream()
.map(DictKeyDO::getKey)
.collect(Collectors.joining(Const.COMMA));
OperatorLogs.add(OperatorLogs.KEY, keys);
// 删除配置项
int effect = dictKeyDAO.deleteBatchIds(idList);
// 删除配置值
dictValueService.deleteDictValueByKeyIdList(idList);
log.info("DictKeyService-deleteDictKeyByIdList effect: {}" , effect);
log.info("DictKeyService-deleteDictKeyByIdList effect: {}", effect);
// 删除缓存
RedisMaps.delete(DictCacheKeyDefine.DICT_KEY, idList);
return effect;

View File

@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.lang.utils.collect.Lists;
import com.orion.ops.framework.biz.operator.log.core.uitls.OperatorLogs;
import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.common.constant.ErrorMessage;
import com.orion.ops.framework.common.utils.Valid;
@@ -15,9 +16,11 @@ import com.orion.ops.module.infra.dao.DictValueDAO;
import com.orion.ops.module.infra.define.cache.DictCacheKeyDefine;
import com.orion.ops.module.infra.entity.domain.DictKeyDO;
import com.orion.ops.module.infra.entity.domain.DictValueDO;
import com.orion.ops.module.infra.entity.domain.HistoryValueDO;
import com.orion.ops.module.infra.entity.dto.DictValueCacheDTO;
import com.orion.ops.module.infra.entity.request.dict.DictValueCreateRequest;
import com.orion.ops.module.infra.entity.request.dict.DictValueQueryRequest;
import com.orion.ops.module.infra.entity.request.dict.DictValueRollbackRequest;
import com.orion.ops.module.infra.entity.request.dict.DictValueUpdateRequest;
import com.orion.ops.module.infra.entity.request.history.HistoryValueCreateRequest;
import com.orion.ops.module.infra.entity.vo.DictValueVO;
@@ -54,7 +57,7 @@ public class DictValueServiceImpl implements DictValueService {
@Override
public Long createDictValue(DictValueCreateRequest request) {
log.info("DictValueService-createDictValue request: {}" , JSON.toJSONString(request));
log.info("DictValueService-createDictValue request: {}", JSON.toJSONString(request));
// 转换
DictValueDO record = DictValueConvert.MAPPER.to(request);
// 查询 dictKey 是否存在
@@ -63,10 +66,11 @@ public class DictValueServiceImpl implements DictValueService {
// 查询数据是否冲突
this.checkDictValuePresent(record);
// 插入
OperatorLogs.add(OperatorLogs.KEY, dictKey);
record.setKey(key);
int effect = dictValueDAO.insert(record);
Long id = record.getId();
log.info("DictValueService-createDictValue id: {}, effect: {}" , id, effect);
log.info("DictValueService-createDictValue id: {}, effect: {}", id, effect);
// 删除缓存
RedisMaps.delete(DictCacheKeyDefine.DICT_VALUE.format(key));
return id;
@@ -74,11 +78,11 @@ public class DictValueServiceImpl implements DictValueService {
@Override
public Integer updateDictValueById(DictValueUpdateRequest request) {
log.info("DictValueService-updateDictValueById id: {}, request: {}" , request.getId(), JSON.toJSONString(request));
log.info("DictValueService-updateDictValueById id: {}, request: {}", request.getId(), JSON.toJSONString(request));
// 查询
Long id = Valid.notNull(request.getId(), ErrorMessage.ID_MISSING);
DictValueDO record = dictValueDAO.selectById(id);
Valid.notNull(record, ErrorMessage.DATA_ABSENT);
Valid.notNull(record, ErrorMessage.CONFIG_ABSENT);
// 查询 dictKey 是否存在
DictKeyDO dictKey = dictKeyDAO.selectById(request.getKeyId());
String key = Valid.notNull(dictKey, ErrorMessage.CONFIG_ABSENT).getKey();
@@ -87,23 +91,41 @@ public class DictValueServiceImpl implements DictValueService {
// 查询数据是否冲突
this.checkDictValuePresent(updateRecord);
// 更新
OperatorLogs.add(OperatorLogs.KEY, dictKey);
updateRecord.setKey(key);
int effect = dictValueDAO.updateById(updateRecord);
log.info("DictValueService-updateDictValueById effect: {}" , effect);
log.info("DictValueService-updateDictValueById effect: {}", effect);
// 删除缓存
RedisMaps.delete(DictCacheKeyDefine.DICT_VALUE.format(key));
// 检查值是否发生改变
String beforeValue = record.getValue();
String afterValue = request.getValue();
if (!beforeValue.equals(afterValue)) {
// 记录历史值
HistoryValueCreateRequest historyRequest = new HistoryValueCreateRequest();
historyRequest.setRelId(id);
historyRequest.setType(HistoryValueTypeEnum.DICT.name());
historyRequest.setBeforeValue(beforeValue);
historyRequest.setAfterValue(afterValue);
historyValueService.createHistoryValue(historyRequest);
// 记录历史归档
this.checkRecordHistory(updateRecord, record);
return effect;
}
@Override
public Integer rollbackDictValueById(DictValueRollbackRequest request) {
Long id = request.getId();
log.info("DictValueService-updateDictValueById id: {}, request: {}", id, JSON.toJSONString(request));
// 查询
DictValueDO record = dictValueDAO.selectById(id);
Valid.notNull(record, ErrorMessage.CONFIG_ABSENT);
// 查询历史值
HistoryValueDO history = historyValueService.getHistoryByRelId(request.getValueId(), id, HistoryValueTypeEnum.DICT.name());
Valid.notNull(history, ErrorMessage.HISTORY_ABSENT);
// 记录日志参数
OperatorLogs.add(OperatorLogs.KEY, record.getKey());
OperatorLogs.add(OperatorLogs.LABEL, record.getLabel());
OperatorLogs.add(OperatorLogs.VALUE, history.getBeforeValue());
// 更新
DictValueDO updateRecord = new DictValueDO();
updateRecord.setId(id);
updateRecord.setValue(history.getBeforeValue());
int effect = dictValueDAO.updateById(updateRecord);
log.info("DictValueService-rollbackDictValueById effect: {}", effect);
// 删除缓存
RedisMaps.delete(DictCacheKeyDefine.DICT_VALUE.format(record.getKey()));
// 记录历史归档
this.checkRecordHistory(updateRecord, record);
return effect;
}
@@ -165,26 +187,33 @@ public class DictValueServiceImpl implements DictValueService {
@Override
public Integer deleteDictValueById(Long id) {
log.info("DictValueService-deleteDictValueById id: {}" , id);
log.info("DictValueService-deleteDictValueById id: {}", id);
// 检查数据是否存在
DictValueDO record = dictValueDAO.selectById(id);
Valid.notNull(record, ErrorMessage.DATA_ABSENT);
Valid.notNull(record, ErrorMessage.CONFIG_ABSENT);
// 添加日志参数
OperatorLogs.add(OperatorLogs.VALUE, record.getKey() + "-" + record.getLabel());
// 删除
return this.deleteDictValue(Lists.singleton(id), Lists.singleton(record));
}
@Override
public Integer deleteDictValueByIdList(List<Long> idList) {
log.info("DictValueService-deleteDictValueByIdList idList: {}" , idList);
log.info("DictValueService-deleteDictValueByIdList idList: {}", idList);
// 查询数据
List<DictValueDO> records = dictValueDAO.selectBatchIds(idList);
// 添加日志参数
String value = records.stream()
.map(s -> s.getKey() + "-" + s.getLabel())
.collect(Collectors.joining(Const.COMMA));
OperatorLogs.add(OperatorLogs.VALUE, value);
// 删除
return this.deleteDictValue(idList, records);
}
@Override
public Integer deleteDictValueByKeyId(Long keyId) {
log.info("DictValueService-deleteDictValueByKeyId keyId: {}" , keyId);
log.info("DictValueService-deleteDictValueByKeyId keyId: {}", keyId);
// 查询数据
List<DictValueDO> records = dictValueDAO.selectList(Conditions.eq(DictValueDO::getKeyId, keyId));
// 删除
@@ -196,7 +225,7 @@ public class DictValueServiceImpl implements DictValueService {
@Override
public Integer deleteDictValueByKeyIdList(List<Long> keyIdList) {
log.info("DictValueService-deleteDictValueByKeyIdList keyIdList: {}" , keyIdList);
log.info("DictValueService-deleteDictValueByKeyIdList keyIdList: {}", keyIdList);
// 查询数据
List<DictValueDO> records = dictValueDAO.selectList(Conditions.in(DictValueDO::getKeyId, keyIdList));
// 删除
@@ -219,7 +248,7 @@ public class DictValueServiceImpl implements DictValueService {
}
// 删除
int effect = dictValueDAO.deleteBatchIds(idList);
log.info("DictValueService-deleteDictValue effect: {}" , effect);
log.info("DictValueService-deleteDictValue effect: {}", effect);
// 删除缓存
List<String> keyList = records.stream()
.map(DictValueDO::getKey)
@@ -230,6 +259,27 @@ public class DictValueServiceImpl implements DictValueService {
return effect;
}
/**
* 检查是否保存默认值
*
* @param updateRecord 修改后
* @param record 修改前
*/
private void checkRecordHistory(DictValueDO updateRecord, DictValueDO record) {
// 检查值是否发生改变
String beforeValue = record.getValue();
String afterValue = updateRecord.getValue();
if (!beforeValue.equals(afterValue)) {
// 记录历史值
HistoryValueCreateRequest historyRequest = new HistoryValueCreateRequest();
historyRequest.setRelId(record.getId());
historyRequest.setType(HistoryValueTypeEnum.DICT.name());
historyRequest.setBeforeValue(beforeValue);
historyRequest.setAfterValue(afterValue);
historyValueService.createHistoryValue(historyRequest);
}
}
/**
* 检查对象是否存在
*

View File

@@ -58,6 +58,17 @@ public class HistoryValueServiceImpl implements HistoryValueService {
return historyValueDAO.selectById(id);
}
@Override
public HistoryValueDO getHistoryByRelId(Long id, Long relId, String type) {
return historyValueDAO.of()
.createWrapper()
.eq(HistoryValueDO::getId, id)
.eq(HistoryValueDO::getRelId, relId)
.eq(HistoryValueDO::getType, type)
.then()
.getOne();
}
@Override
public Integer deleteByRelId(String type, Long relId) {
log.info("HistoryValueService-deleteByRelId type: {}, relId: {}", type, relId);