优化用户重置密码加密+物料照片批量导入

This commit is contained in:
2026-03-05 14:39:20 +08:00
parent 656267e6b7
commit 5726239eda
8 changed files with 219 additions and 118 deletions

View File

@@ -15,5 +15,6 @@ import java.util.List;
@Repository @Repository
public interface MaterialInfoMapper extends BaseMapper<MaterialInfoDO> { public interface MaterialInfoMapper extends BaseMapper<MaterialInfoDO> {
public int updateByName(List<MaterialInfoDO> list); public int updateByName(List<MaterialInfoDO> list);
public int updateByCode(List<MaterialInfoDO> list); public int updateByCode(List<MaterialInfoDO> list);
} }

View File

@@ -1,6 +1,5 @@
package top.wms.admin.material.model.resp; package top.wms.admin.material.model.resp;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;

View File

@@ -10,9 +10,6 @@ import top.wms.admin.material.model.req.MaterialInfoReq;
import top.wms.admin.material.model.resp.MaterialImportParseResp; import top.wms.admin.material.model.resp.MaterialImportParseResp;
import top.wms.admin.material.model.resp.MaterialInfoImportResp; import top.wms.admin.material.model.resp.MaterialInfoImportResp;
import top.wms.admin.material.model.resp.MaterialInfoResp; import top.wms.admin.material.model.resp.MaterialInfoResp;
import top.wms.admin.system.model.req.user.UserImportReq;
import top.wms.admin.system.model.resp.user.UserImportParseResp;
import top.wms.admin.system.model.resp.user.UserImportResp;
import java.io.IOException; import java.io.IOException;
@@ -53,4 +50,9 @@ public interface MaterialInfoService extends BaseService<MaterialInfoResp, Mater
* @return 导入结果 * @return 导入结果
*/ */
MaterialInfoImportResp importMaterial(MaterialInfoImportReq req); MaterialInfoImportResp importMaterial(MaterialInfoImportReq req);
/*
* 照片批量上传处理
* */
void uploadMaterialPhotos(MultipartFile file);
} }

View File

@@ -2,40 +2,35 @@ package top.wms.admin.material.service.impl;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.UUID; import cn.hutool.core.lang.UUID;
import cn.hutool.core.math.MathUtil;
import cn.hutool.core.util.*; import cn.hutool.core.util.*;
import cn.hutool.extra.validation.ValidationUtil; import cn.hutool.extra.validation.ValidationUtil;
import cn.hutool.http.ContentType; import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel; import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import me.ahoo.cosid.IdGenerator; import lombok.extern.slf4j.Slf4j;
import me.ahoo.cosid.provider.DefaultIdGeneratorProvider;
import net.dreamlu.mica.core.result.R; import net.dreamlu.mica.core.result.R;
import org.dromara.x.file.storage.core.FileInfo;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import top.continew.starter.cache.redisson.util.RedisUtils; import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.exception.BusinessException; import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.core.util.SpringUtils;
import top.continew.starter.core.validation.CheckUtils; import top.continew.starter.core.validation.CheckUtils;
import top.continew.starter.extension.crud.service.BaseServiceImpl; import top.continew.starter.extension.crud.service.BaseServiceImpl;
import top.continew.starter.web.util.FileUploadUtils; import top.continew.starter.web.util.FileUploadUtils;
import top.wms.admin.common.constant.CacheConstants; import top.wms.admin.common.constant.CacheConstants;
import top.wms.admin.common.context.UserContextHolder; import top.wms.admin.common.context.UserContextHolder;
import top.wms.admin.common.enums.GenderEnum;
import top.wms.admin.common.util.SecureUtils; import top.wms.admin.common.util.SecureUtils;
import top.wms.admin.material.mapper.MaterialInfoMapper; import top.wms.admin.material.mapper.MaterialInfoMapper;
import top.wms.admin.material.model.entity.MaterialInfoDO; import top.wms.admin.material.model.entity.MaterialInfoDO;
@@ -47,23 +42,19 @@ import top.wms.admin.material.model.resp.MaterialImportParseResp;
import top.wms.admin.material.model.resp.MaterialInfoImportResp; import top.wms.admin.material.model.resp.MaterialInfoImportResp;
import top.wms.admin.material.model.resp.MaterialInfoResp; import top.wms.admin.material.model.resp.MaterialInfoResp;
import top.wms.admin.material.service.MaterialInfoService; import top.wms.admin.material.service.MaterialInfoService;
import top.wms.admin.system.model.entity.DeptDO; import top.wms.admin.system.service.FileService;
import top.wms.admin.system.model.entity.RoleDO;
import top.wms.admin.system.model.entity.UserDO;
import top.wms.admin.system.model.entity.UserRoleDO;
import top.wms.admin.system.model.req.user.UserImportReq;
import top.wms.admin.system.model.req.user.UserImportRowReq;
import top.wms.admin.system.model.resp.user.UserImportParseResp;
import top.wms.admin.system.model.resp.user.UserImportResp;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal;
import java.sql.Time;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import static top.wms.admin.system.enums.ImportPolicyEnum.*; import static top.wms.admin.system.enums.ImportPolicyEnum.*;
import static top.wms.admin.system.enums.ImportPolicyEnum.SKIP; import static top.wms.admin.system.enums.ImportPolicyEnum.SKIP;
@@ -76,8 +67,13 @@ import static top.wms.admin.system.enums.ImportPolicyEnum.SKIP;
*/ */
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper, MaterialInfoDO, MaterialInfoResp, MaterialInfoResp, MaterialInfoQuery, MaterialInfoReq> implements MaterialInfoService { public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper, MaterialInfoDO, MaterialInfoResp, MaterialInfoResp, MaterialInfoQuery, MaterialInfoReq> implements MaterialInfoService {
private static final Set<String> IMAGE_EXTENSIONS = Set.of("jpg", "jpeg", "png", "gif", "bmp");
private final FileService fileService;
@Override @Override
public MaterialInfoDO getMaterialInfoByCode(String code) { public MaterialInfoDO getMaterialInfoByCode(String code) {
if (StrUtil.isNotBlank(code)) { if (StrUtil.isNotBlank(code)) {
@@ -90,7 +86,8 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
@Override @Override
public void downloadImportTemplate(HttpServletResponse response) throws IOException { public void downloadImportTemplate(HttpServletResponse response) throws IOException {
try { try {
FileUploadUtils.download(response, ResourceUtil.getStream("templates/import/materialInfo.xlsx"), "物料信息导入模板.xlsx"); FileUploadUtils.download(response, ResourceUtil
.getStream("templates/import/materialInfo.xlsx"), "物料信息导入模板.xlsx");
} catch (Exception e) { } catch (Exception e) {
log.error("下载用户导入模板失败:{}", e); log.error("下载用户导入模板失败:{}", e);
response.setCharacterEncoding(CharsetUtil.UTF_8); response.setCharacterEncoding(CharsetUtil.UTF_8);
@@ -143,7 +140,6 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
return materialImportResp; return materialImportResp;
} }
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public MaterialInfoImportResp importMaterial(MaterialInfoImportReq req) { public MaterialInfoImportResp importMaterial(MaterialInfoImportReq req) {
@@ -160,8 +156,7 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
// 已存在数据查询 // 已存在数据查询
List<String> existName = listExistByField(importMaterialList, MaterialImportRowReq::getMaterialName, MaterialInfoDO::getMaterialName); List<String> existName = listExistByField(importMaterialList, MaterialImportRowReq::getMaterialName, MaterialInfoDO::getMaterialName);
List<String> existCode = listExistByField(importMaterialList, MaterialImportRowReq::getEncoding, MaterialInfoDO::getEncoding); List<String> existCode = listExistByField(importMaterialList, MaterialImportRowReq::getEncoding, MaterialInfoDO::getEncoding);
CheckUtils CheckUtils.throwIf(isExitImportMaterial(req, importMaterialList, existName, existCode), "数据不符合导入策略,已退出导入");
.throwIf(isExitImportMaterial(req, importMaterialList, existName, existCode), "数据不符合导入策略,已退出导入");
// 批量操作数据库集合 // 批量操作数据库集合
List<MaterialInfoDO> insertList = new ArrayList<>(); List<MaterialInfoDO> insertList = new ArrayList<>();
List<MaterialInfoDO> updateByNameList = new ArrayList<>(); List<MaterialInfoDO> updateByNameList = new ArrayList<>();
@@ -199,17 +194,12 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
int updateByCodeCount = updateByCodeList.size(); int updateByCodeCount = updateByCodeList.size();
int totalUpdateCount = updateByNameCount + updateByCodeCount; int totalUpdateCount = updateByNameCount + updateByCodeCount;
int totalHandleCount = insertCount + totalUpdateCount; int totalHandleCount = insertCount + totalUpdateCount;
return new MaterialInfoImportResp( return new MaterialInfoImportResp(totalHandleCount, // 总处理数
totalHandleCount, // 总处理数
insertCount, // 新增数 insertCount, // 新增数
totalUpdateCount // 更新总数 totalUpdateCount // 更新总数
); );
} }
/** /**
* 按指定数据集获取数据库已存在的数量 * 按指定数据集获取数据库已存在的数量
* *
@@ -230,7 +220,6 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
.in(dbField, fieldEncrypt ? SecureUtils.encryptFieldByAes(fieldValues) : fieldValues)); .in(dbField, fieldEncrypt ? SecureUtils.encryptFieldByAes(fieldValues) : fieldValues));
} }
/** /**
* 过滤无效的导入用户数据(批量导入不严格校验数据) * 过滤无效的导入用户数据(批量导入不严格校验数据)
* *
@@ -243,7 +232,8 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
.toList(); .toList();
// 物料名去重 // 物料名去重
return list.stream() return list.stream()
.collect(Collectors.toMap(MaterialImportRowReq::getMaterialName, row -> row, (existing, replacement) -> existing)) .collect(Collectors.toMap(MaterialImportRowReq::getMaterialName, row -> row, (existing,
replacement) -> existing))
.values() .values()
.stream() .stream()
.toList(); .toList();
@@ -284,8 +274,8 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
List<String> existName, List<String> existName,
List<String> existCode) { List<String> existCode) {
return list.stream() return list.stream()
.anyMatch(row -> EXIT.validate(req.getDuplicateName(), row.getMaterialName(), existName) || EXIT.validate(req .anyMatch(row -> EXIT.validate(req.getDuplicateName(), row.getMaterialName(), existName) || EXIT
.getDuplicateCode(), row.getEncoding(), existCode)); .validate(req.getDuplicateCode(), row.getEncoding(), existCode));
} }
/** /**
@@ -312,7 +302,9 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
* @param updateByNameList 修改用户 * @param updateByNameList 修改用户
* @param updateByCodeList 用户角色关联 * @param updateByCodeList 用户角色关联
*/ */
private void doImportMaterial(List<MaterialInfoDO> insertList, List<MaterialInfoDO> updateByNameList, List<MaterialInfoDO> updateByCodeList) { private void doImportMaterial(List<MaterialInfoDO> insertList,
List<MaterialInfoDO> updateByNameList,
List<MaterialInfoDO> updateByCodeList) {
if (CollUtil.isNotEmpty(insertList)) { if (CollUtil.isNotEmpty(insertList)) {
baseMapper.insertBatch(insertList); baseMapper.insertBatch(insertList);
} }
@@ -323,4 +315,102 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
baseMapper.updateByCode(updateByCodeList); baseMapper.updateByCode(updateByCodeList);
} }
} }
@Override
public void uploadMaterialPhotos(MultipartFile zipFile) {
// 1. 初始化物料编码-照片地址Map
Map<String, String> codeUrlMap = new HashMap<>();
// 物料照片存储路径(自定义,比如按日期分目录)
String photoStoragePath = "/" + DateUtil.today() + "/";
try (ZipInputStream zipInputStream = new ZipInputStream(zipFile.getInputStream())) {
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
// 跳过目录、非图片文件
if (zipEntry.isDirectory() || !isImageFile(zipEntry.getName())) {
zipInputStream.closeEntry();
continue;
}
// 2. 提取物料编码(照片名 = 物料编码,去掉后缀)
String fileName = zipEntry.getName();
log.info("正在处理的照片:" + fileName);
//去除windows或linux环境下 可能存在的多层级目录
if (fileName.contains("/")) {
fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
}
if (fileName.contains("\\")) {
fileName = fileName.substring(fileName.lastIndexOf("\\") + 1);
}
String materialCode = fileName.substring(0, fileName.lastIndexOf("."));
// 3. 读取ZIP中的图片为BufferedImage
BufferedImage image = ImageIO.read(zipInputStream);
if (ObjectUtil.isEmpty(image)) {
log.warn("无法读取图片: {}", fileName);
zipInputStream.closeEntry();
continue;
}
// 4. 将BufferedImage转为字节流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String imageExt = getImageExtension(fileName);
ImageIO.write(image, imageExt, baos);
byte[] imageBytes = baos.toByteArray();
// 5. 转换为MultipartFile使用MockMultipartFile
MultipartFile singleImageFile = new MockMultipartFile("file", fileName, "image/" + (imageExt
.equals("jpg") ? "jpeg" : imageExt), imageBytes);
// 6. 调用upload方法上传图片
FileInfo fileInfo = fileService.upload(singleImageFile, photoStoragePath, null, true, true);
// 7. 将物料编码和图片URL存入Map
codeUrlMap.put(materialCode, fileInfo.getUrl());
zipInputStream.closeEntry();
}
setPhotosByCode(codeUrlMap);
} catch (Exception e) {
throw new BusinessException("照片批量导入失败:" + e.getMessage());
}
}
private boolean isImageFile(String fileName) {
if (fileName == null || !fileName.contains("."))
return false;
String ext = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
return IMAGE_EXTENSIONS.contains(ext);
}
private String getImageExtension(String fileName) {
String ext = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
return "jpeg".equals(ext) ? "jpg" : ext;
}
private void setPhotosByCode(Map<String, String> codeUrlMap) {
CheckUtils.throwIfEmpty(codeUrlMap, "照片为空,请重新上传");
List<MaterialInfoDO> existList = baseMapper.selectList(Wrappers.<MaterialInfoDO>lambdaQuery()
.in(MaterialInfoDO::getEncoding, codeUrlMap.keySet()));
if (existList.isEmpty()) {
log.warn("未找到任何匹配的物料编码");
return;
}
List<MaterialInfoDO> updateList = existList.stream().map(exist -> {
MaterialInfoDO updateDO = new MaterialInfoDO();
updateDO.setId(exist.getId());
updateDO.setPhotoUrl(codeUrlMap.get(exist.getEncoding()));
return updateDO;
}).collect(Collectors.toList());
if (!updateList.isEmpty()) {
baseMapper.updateBatchById(updateList);
log.info("成功更新 {} 个物料的照片", updateList.size());
}
//记录未找到的物料编码(可选,方便排查问题)
Set<String> existCodes = existList.stream().map(MaterialInfoDO::getEncoding).collect(Collectors.toSet());
codeUrlMap.keySet()
.stream()
.filter(code -> !existCodes.contains(code))
.forEach(code -> log.warn("物料编码 [{}] 不存在,照片更新失败", code));
}
} }

View File

@@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.*;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping; import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.web.model.R;
import top.wms.admin.common.controller.BaseController; import top.wms.admin.common.controller.BaseController;
import top.wms.admin.material.model.entity.MaterialInfoDO; import top.wms.admin.material.model.entity.MaterialInfoDO;
import top.wms.admin.material.model.query.MaterialInfoQuery; import top.wms.admin.material.model.query.MaterialInfoQuery;
@@ -25,9 +26,6 @@ import top.wms.admin.material.model.resp.MaterialImportParseResp;
import top.wms.admin.material.model.resp.MaterialInfoImportResp; import top.wms.admin.material.model.resp.MaterialInfoImportResp;
import top.wms.admin.material.model.resp.MaterialInfoResp; import top.wms.admin.material.model.resp.MaterialInfoResp;
import top.wms.admin.material.service.MaterialInfoService; import top.wms.admin.material.service.MaterialInfoService;
import top.wms.admin.system.model.req.user.UserImportReq;
import top.wms.admin.system.model.resp.user.UserImportParseResp;
import top.wms.admin.system.model.resp.user.UserImportResp;
import java.io.IOException; import java.io.IOException;
@@ -70,4 +68,15 @@ public class MaterialInfoController extends BaseController<MaterialInfoService,
public MaterialInfoImportResp importUser(@Validated @RequestBody MaterialInfoImportReq req) { public MaterialInfoImportResp importUser(@Validated @RequestBody MaterialInfoImportReq req) {
return baseService.importMaterial(req); return baseService.importMaterial(req);
} }
@Operation(summary = "照片批量导入", description = "照片批量导入")
@SaCheckPermission("admin:materialInfo:import")
@PostMapping("/import/uploadMaterialPhotos")
public R uploadMaterialPhotos(@RequestParam("zipFile") @NotNull(message = "文件不能为空") MultipartFile zipFile) {
ValidationUtils.throwIf(zipFile::isEmpty, "文件不能为空");
String originalFilename = zipFile.getOriginalFilename();
ValidationUtils.throwIf(() -> originalFilename == null || !originalFilename.endsWith(".zip"), "仅支持上传ZIP格式文件");
baseService.uploadMaterialPhotos(zipFile);
return R.ok();
}
} }