Merge remote-tracking branch 'base-version/main' into dev
This commit is contained in:
@@ -1,11 +1,20 @@
|
||||
package com.zt.plat.module.system.controller.admin.dict;
|
||||
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.ExcelWriter;
|
||||
import com.alibaba.excel.write.metadata.WriteSheet;
|
||||
import com.zt.plat.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||
import com.zt.plat.framework.common.pojo.PageParam;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.framework.common.util.http.HttpUtils;
|
||||
import com.zt.plat.framework.common.util.object.BeanUtils;
|
||||
import com.zt.plat.framework.excel.core.handler.SelectSheetWriteHandler;
|
||||
import com.zt.plat.framework.excel.core.util.ExcelUtils;
|
||||
import com.alibaba.excel.converters.longconverter.LongStringConverter;
|
||||
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictImportExcelVO;
|
||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictImportRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypeRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO;
|
||||
@@ -14,6 +23,7 @@ import com.zt.plat.module.system.dal.dataobject.dict.DictTypeDO;
|
||||
import com.zt.plat.module.system.service.dict.DictTypeService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@@ -21,8 +31,10 @@ import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static com.zt.plat.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||
@@ -99,4 +111,45 @@ public class DictTypeController {
|
||||
BeanUtils.toBean(list, DictTypeRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/get-import-template")
|
||||
@Operation(summary = "获得字典导入模板")
|
||||
public void importTemplate(HttpServletResponse response) throws IOException {
|
||||
List<DictImportExcelVO> samples = Arrays.asList(
|
||||
DictImportExcelVO.builder()
|
||||
.dictTypeName("性别").dictType("system_user_sex").dictTypeRemark("系统内置示例")
|
||||
.label("男").value("1").sort(1).colorType("primary").dataRemark("示例数据").build(),
|
||||
DictImportExcelVO.builder()
|
||||
.dictTypeName("证件类型").dictType("system_id_card_type").dictTypeRemark("自定义示例")
|
||||
.label("身份证").value("ID").sort(1).dataRemark("示例数据").build()
|
||||
);
|
||||
|
||||
try (ExcelWriter writer = EasyExcel.write(response.getOutputStream())
|
||||
.autoCloseStream(false)
|
||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||
.registerConverter(new LongStringConverter())
|
||||
.build()) {
|
||||
WriteSheet sheet = EasyExcel.writerSheet(0, "字典导入")
|
||||
.head(DictImportExcelVO.class)
|
||||
.registerWriteHandler(new SelectSheetWriteHandler(DictImportExcelVO.class))
|
||||
.build();
|
||||
writer.write(samples, sheet);
|
||||
}
|
||||
|
||||
response.addHeader("Content-Disposition",
|
||||
"attachment;filename=" + HttpUtils.encodeUtf8("字典导入模板.xls"));
|
||||
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
|
||||
}
|
||||
|
||||
@PostMapping("/import")
|
||||
@Operation(summary = "导入字典")
|
||||
@Parameters({
|
||||
@Parameter(name = "file", description = "Excel 文件", required = true)
|
||||
})
|
||||
@PreAuthorize("@ss.hasPermission('system:dict:import')")
|
||||
public CommonResult<DictImportRespVO> importDict(@RequestParam("file") MultipartFile file) throws IOException {
|
||||
List<DictImportExcelVO> importList = ExcelUtils.read(file, DictImportExcelVO.class, 0);
|
||||
DictImportRespVO respVO = dictTypeService.importDictList(importList);
|
||||
return success(respVO);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.zt.plat.module.system.controller.admin.dict.vo.data;
|
||||
|
||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictImportExcelVO;
|
||||
|
||||
/**
|
||||
* @deprecated 迁移到单工作表导入模型 {@link DictImportExcelVO}
|
||||
*/
|
||||
@Deprecated
|
||||
public final class DictDataImportExcelVO {
|
||||
|
||||
private DictDataImportExcelVO() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.zt.plat.module.system.controller.admin.dict.vo.type;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 字典导入 Excel VO(单行同时包含字典类型与字典数据)
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = false)
|
||||
public class DictImportExcelVO {
|
||||
|
||||
/**
|
||||
* 字典名称
|
||||
*/
|
||||
@ExcelProperty("字典名称")
|
||||
private String dictTypeName;
|
||||
|
||||
/**
|
||||
* 字典类型
|
||||
*/
|
||||
@ExcelProperty("字典类型")
|
||||
private String dictType;
|
||||
|
||||
/**
|
||||
* 字典类型备注
|
||||
*/
|
||||
@ExcelProperty("类型备注")
|
||||
private String dictTypeRemark;
|
||||
|
||||
/**
|
||||
* 字典标签
|
||||
*/
|
||||
@ExcelProperty("字典标签")
|
||||
private String label;
|
||||
|
||||
/**
|
||||
* 字典键值
|
||||
*/
|
||||
@ExcelProperty("字典键值")
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
@ExcelProperty("排序")
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 颜色类型
|
||||
*/
|
||||
@ExcelProperty("颜色类型")
|
||||
private String colorType;
|
||||
|
||||
/**
|
||||
* CSS 样式
|
||||
*/
|
||||
@ExcelProperty("CSS 样式")
|
||||
private String cssClass;
|
||||
|
||||
/**
|
||||
* 字典数据备注
|
||||
*/
|
||||
@ExcelProperty("数据备注")
|
||||
private String dataRemark;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.zt.plat.module.system.controller.admin.dict.vo.type;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 字典导入响应 VO
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@Schema(description = "管理后台 - 字典导入 Response VO")
|
||||
public class DictImportRespVO {
|
||||
|
||||
@Schema(description = "创建成功的字典类型名称列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<String> createDictTypeNames;
|
||||
|
||||
@Schema(description = "更新成功的字典类型名称列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<String> updateDictTypeNames;
|
||||
|
||||
@Schema(description = "导入失败的字典类型集合,key 为字典名称,value 为失败原因", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Map<String, String> failureDictTypeNames;
|
||||
|
||||
@Schema(description = "创建成功的字典数据标识列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<String> createDictDataKeys;
|
||||
|
||||
@Schema(description = "更新成功的字典数据标识列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<String> updateDictDataKeys;
|
||||
|
||||
@Schema(description = "导入失败的字典数据集合,key 为字典数据标识,value 为失败原因", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Map<String, String> failureDictDataKeys;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.zt.plat.module.system.controller.admin.dict.vo.type;
|
||||
|
||||
/**
|
||||
* @deprecated 保留空壳文件以兼容历史引用,新的导入请使用 {@link DictImportExcelVO}
|
||||
*/
|
||||
@Deprecated
|
||||
public final class DictTypeImportExcelVO {
|
||||
|
||||
private DictTypeImportExcelVO() {
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package com.zt.plat.module.system.service.dict;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictImportExcelVO;
|
||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictImportRespVO;
|
||||
import com.zt.plat.module.system.dal.dataobject.dict.DictTypeDO;
|
||||
|
||||
import java.util.List;
|
||||
@@ -67,4 +69,12 @@ public interface DictTypeService {
|
||||
*/
|
||||
List<DictTypeDO> getDictTypeList();
|
||||
|
||||
/**
|
||||
* 导入字典类型与字典数据(单工作表)
|
||||
*
|
||||
* @param importList 导入行列表
|
||||
* @return 导入结果
|
||||
*/
|
||||
DictImportRespVO importDictList(List<DictImportExcelVO> importList);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +1,36 @@
|
||||
package com.zt.plat.module.system.service.dict;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.zt.plat.framework.common.enums.CommonStatusEnum;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.framework.common.util.collection.CollectionUtils;
|
||||
import com.zt.plat.framework.common.util.date.LocalDateTimeUtils;
|
||||
import com.zt.plat.framework.common.util.object.BeanUtils;
|
||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictImportExcelVO;
|
||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictImportRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO;
|
||||
import com.zt.plat.module.system.dal.dataobject.dict.DictDataDO;
|
||||
import com.zt.plat.module.system.dal.dataobject.dict.DictTypeDO;
|
||||
import com.zt.plat.module.system.dal.mysql.dict.DictTypeMapper;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.zt.plat.module.system.enums.ErrorCodeConstants.*;
|
||||
@@ -24,6 +41,7 @@ import static com.zt.plat.module.system.enums.ErrorCodeConstants.*;
|
||||
* @author ZT
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class DictTypeServiceImpl implements DictTypeService {
|
||||
|
||||
@Resource
|
||||
@@ -92,6 +110,128 @@ public class DictTypeServiceImpl implements DictTypeService {
|
||||
return dictTypeMapper.selectList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DictImportRespVO importDictList(List<DictImportExcelVO> importList) {
|
||||
if (CollUtil.isEmpty(importList)) {
|
||||
throw exception(DICT_IMPORT_LIST_IS_EMPTY);
|
||||
}
|
||||
|
||||
Map<String, DictTypeDO> typeByType = new HashMap<>();
|
||||
Map<String, DictTypeDO> typeByName = new HashMap<>();
|
||||
List<DictTypeDO> existingTypes = dictTypeMapper.selectList();
|
||||
existingTypes.forEach(item -> {
|
||||
typeByType.put(item.getType(), item);
|
||||
typeByName.put(item.getName(), item);
|
||||
});
|
||||
|
||||
List<String> createTypeNames = new ArrayList<>();
|
||||
Map<String, String> failureTypeNames = new LinkedHashMap<>();
|
||||
List<String> createDataKeys = new ArrayList<>();
|
||||
Map<String, String> failureDataKeys = new LinkedHashMap<>();
|
||||
|
||||
Map<String, Map<String, DictDataDO>> dataCacheByType = new HashMap<>();
|
||||
Set<String> seenRowKeys = new HashSet<>();
|
||||
|
||||
for (DictImportExcelVO row : importList) {
|
||||
String dictTypeName = trimToNull(row.getDictTypeName());
|
||||
String dictTypeCode = trimToNull(row.getDictType());
|
||||
String label = trimToNull(row.getLabel());
|
||||
String value = trimToNull(row.getValue());
|
||||
String displayTypeKey = StrUtil.nullToDefault(dictTypeName, StrUtil.nullToDefault(dictTypeCode, "未知字典"));
|
||||
String displayDataKey = String.format("%s-%s", displayTypeKey, StrUtil.nullToDefault(label, "未知标签"));
|
||||
|
||||
if (StrUtil.isEmpty(dictTypeName) || StrUtil.isEmpty(dictTypeCode)) {
|
||||
failureTypeNames.put(displayTypeKey, "字典名称与字典类型均不能为空");
|
||||
continue;
|
||||
}
|
||||
if (StrUtil.isEmpty(label) || StrUtil.isEmpty(value)) {
|
||||
failureDataKeys.put(displayDataKey, "字典标签和值不能为空");
|
||||
continue;
|
||||
}
|
||||
String rowKey = dictTypeCode.toLowerCase(Locale.ROOT) + "::" + value.toLowerCase(Locale.ROOT);
|
||||
if (!seenRowKeys.add(rowKey)) {
|
||||
failureDataKeys.put(displayDataKey, "Excel 中存在重复的字典数据");
|
||||
continue;
|
||||
}
|
||||
|
||||
DictTypeDO dictType = typeByType.get(dictTypeCode);
|
||||
if (dictType == null) {
|
||||
dictType = typeByName.get(dictTypeName);
|
||||
}
|
||||
try {
|
||||
if (dictType == null) {
|
||||
DictTypeSaveReqVO typeReq = new DictTypeSaveReqVO();
|
||||
typeReq.setName(dictTypeName);
|
||||
typeReq.setType(dictTypeCode);
|
||||
typeReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
typeReq.setRemark(trimToNull(row.getDictTypeRemark()));
|
||||
Long id = createDictType(typeReq);
|
||||
dictType = dictTypeMapper.selectById(id);
|
||||
if (dictType == null) {
|
||||
dictType = new DictTypeDO();
|
||||
dictType.setId(id);
|
||||
dictType.setName(typeReq.getName());
|
||||
dictType.setType(typeReq.getType());
|
||||
dictType.setStatus(typeReq.getStatus());
|
||||
dictType.setRemark(typeReq.getRemark());
|
||||
}
|
||||
typeByType.put(dictType.getType(), dictType);
|
||||
typeByName.put(dictType.getName(), dictType);
|
||||
createTypeNames.add(dictType.getName());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
String message = ex.getMessage();
|
||||
failureTypeNames.put(displayTypeKey, StrUtil.blankToDefault(message, "导入失败"));
|
||||
log.warn("Import dict type failed, key={}, message=", displayTypeKey, ex);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, DictDataDO> cache = dataCacheByType.computeIfAbsent(dictType.getType(),
|
||||
key -> CollectionUtils.convertMap(dictDataService.getDictDataListByDictType(key), DictDataDO::getValue));
|
||||
DictDataDO existsData = cache.get(value);
|
||||
if (existsData != null) {
|
||||
failureDataKeys.put(displayDataKey, "字典数据已存在,不允许重复导入");
|
||||
continue;
|
||||
}
|
||||
DictDataSaveReqVO dataReq = new DictDataSaveReqVO();
|
||||
dataReq.setDictType(dictType.getType());
|
||||
dataReq.setLabel(label);
|
||||
dataReq.setValue(value);
|
||||
dataReq.setSort(ObjectUtil.defaultIfNull(row.getSort(), 0));
|
||||
dataReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
dataReq.setColorType(trimToNull(row.getColorType()));
|
||||
dataReq.setCssClass(trimToNull(row.getCssClass()));
|
||||
dataReq.setRemark(trimToNull(row.getDataRemark()));
|
||||
Long dataId = dictDataService.createDictData(dataReq);
|
||||
DictDataDO created = dictDataService.getDictData(dataId);
|
||||
if (created == null) {
|
||||
created = new DictDataDO();
|
||||
created.setId(dataId);
|
||||
created.setDictType(dataReq.getDictType());
|
||||
created.setLabel(dataReq.getLabel());
|
||||
created.setValue(dataReq.getValue());
|
||||
}
|
||||
cache.put(created.getValue(), created);
|
||||
createDataKeys.add(displayDataKey);
|
||||
} catch (Exception ex) {
|
||||
String message = ex.getMessage();
|
||||
failureDataKeys.put(displayDataKey, StrUtil.blankToDefault(message, "导入失败"));
|
||||
log.warn("Import dict data failed, key={}, message=", displayDataKey, ex);
|
||||
}
|
||||
}
|
||||
|
||||
return DictImportRespVO.builder()
|
||||
.createDictTypeNames(createTypeNames)
|
||||
.updateDictTypeNames(List.of())
|
||||
.failureDictTypeNames(failureTypeNames)
|
||||
.createDictDataKeys(createDataKeys)
|
||||
.updateDictDataKeys(List.of())
|
||||
.failureDictDataKeys(failureDataKeys)
|
||||
.build();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateDictTypeNameUnique(Long id, String name) {
|
||||
DictTypeDO dictType = dictTypeMapper.selectByName(name);
|
||||
@@ -137,4 +277,12 @@ public class DictTypeServiceImpl implements DictTypeService {
|
||||
return dictType;
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String trim = StrUtil.trim(value);
|
||||
return StrUtil.isEmpty(trim) ? null : trim;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user