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

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
public interface MaterialInfoMapper extends BaseMapper<MaterialInfoDO> {
public int updateByName(List<MaterialInfoDO> list);
public int updateByCode(List<MaterialInfoDO> list);
}

View File

@@ -1,6 +1,5 @@
package top.wms.admin.material.model.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
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.MaterialInfoImportResp;
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;
@@ -53,4 +50,9 @@ public interface MaterialInfoService extends BaseService<MaterialInfoResp, Mater
* @return 导入结果
*/
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.collection.CollUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.math.MathUtil;
import cn.hutool.core.util.*;
import cn.hutool.extra.validation.ValidationUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
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.support.SFunction;
import jakarta.servlet.http.HttpServletResponse;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import me.ahoo.cosid.IdGenerator;
import me.ahoo.cosid.provider.DefaultIdGeneratorProvider;
import lombok.extern.slf4j.Slf4j;
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.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import top.continew.starter.cache.redisson.util.RedisUtils;
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.extension.crud.service.BaseServiceImpl;
import top.continew.starter.web.util.FileUploadUtils;
import top.wms.admin.common.constant.CacheConstants;
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.material.mapper.MaterialInfoMapper;
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.MaterialInfoResp;
import top.wms.admin.material.service.MaterialInfoService;
import top.wms.admin.system.model.entity.DeptDO;
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 top.wms.admin.system.service.FileService;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.sql.Time;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
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.SKIP;
@@ -76,13 +67,18 @@ import static top.wms.admin.system.enums.ImportPolicyEnum.SKIP;
*/
@Service
@RequiredArgsConstructor
@Slf4j
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
public MaterialInfoDO getMaterialInfoByCode(String code) {
if(StrUtil.isNotBlank(code)){
if (StrUtil.isNotBlank(code)) {
return baseMapper.lambdaQuery().eq(MaterialInfoDO::getEncoding, code).one();
}else{
} else {
return null;
}
}
@@ -90,7 +86,8 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
@Override
public void downloadImportTemplate(HttpServletResponse response) throws IOException {
try {
FileUploadUtils.download(response, ResourceUtil.getStream("templates/import/materialInfo.xlsx"), "物料信息导入模板.xlsx");
FileUploadUtils.download(response, ResourceUtil
.getStream("templates/import/materialInfo.xlsx"), "物料信息导入模板.xlsx");
} catch (Exception e) {
log.error("下载用户导入模板失败:{}", e);
response.setCharacterEncoding(CharsetUtil.UTF_8);
@@ -143,7 +140,6 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
return materialImportResp;
}
@Override
@Transactional(rollbackFor = Exception.class)
public MaterialInfoImportResp importMaterial(MaterialInfoImportReq req) {
@@ -160,22 +156,21 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
// 已存在数据查询
List<String> existName = listExistByField(importMaterialList, MaterialImportRowReq::getMaterialName, MaterialInfoDO::getMaterialName);
List<String> existCode = listExistByField(importMaterialList, MaterialImportRowReq::getEncoding, MaterialInfoDO::getEncoding);
CheckUtils
.throwIf(isExitImportMaterial(req, importMaterialList, existName, existCode), "数据不符合导入策略,已退出导入");
CheckUtils.throwIf(isExitImportMaterial(req, importMaterialList, existName, existCode), "数据不符合导入策略,已退出导入");
// 批量操作数据库集合
List<MaterialInfoDO> insertList = new ArrayList<>();
List<MaterialInfoDO> updateByNameList = new ArrayList<>();
List<MaterialInfoDO> updateByCodeList = new ArrayList<>();
// ID生成器
// IdGenerator idGenerator = DefaultIdGeneratorProvider.INSTANCE.getShare();
// IdGenerator idGenerator = DefaultIdGeneratorProvider.INSTANCE.getShare();
for (MaterialImportRowReq row : importMaterialList) {
if (isSkipMaterialImport(req, row, existName, existCode)) {
// 按规则跳过该行
continue;
}
MaterialInfoDO materialDO = BeanUtil.toBeanIgnoreError(row, MaterialInfoDO.class);
materialDO.setUnitWeight(NumberUtil.isValidNumber(row.getUnitWeight())?row.getUnitWeight(): null);
materialDO.setMaterialSpec(StrUtil.isNotBlank(row.getMaterialSpec())?row.getMaterialSpec():null);
materialDO.setUnitWeight(NumberUtil.isValidNumber(row.getUnitWeight()) ? row.getUnitWeight() : null);
materialDO.setMaterialSpec(StrUtil.isNotBlank(row.getMaterialSpec()) ? row.getMaterialSpec() : null);
// 修改 or 新增
if (UPDATE.validate(req.getDuplicateName(), row.getMaterialName(), existName)) {
materialDO.setMaterialName(row.getMaterialName());
@@ -188,7 +183,7 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
materialDO.setUpdateUser(UserContextHolder.getUserId());
updateByCodeList.add(materialDO);
} else {
// materialDO.setId(idGenerator.generate());
// materialDO.setId(idGenerator.generate());
insertList.add(materialDO);
}
}
@@ -199,17 +194,12 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
int updateByCodeCount = updateByCodeList.size();
int totalUpdateCount = updateByNameCount + updateByCodeCount;
int totalHandleCount = insertCount + totalUpdateCount;
return new MaterialInfoImportResp(
totalHandleCount, // 总处理数
return new MaterialInfoImportResp(totalHandleCount, // 总处理数
insertCount, // 新增数
totalUpdateCount // 更新总数
);
}
/**
* 按指定数据集获取数据库已存在的数量
*
@@ -230,7 +220,6 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
.in(dbField, fieldEncrypt ? SecureUtils.encryptFieldByAes(fieldValues) : fieldValues));
}
/**
* 过滤无效的导入用户数据(批量导入不严格校验数据)
*
@@ -243,7 +232,8 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
.toList();
// 物料名去重
return list.stream()
.collect(Collectors.toMap(MaterialImportRowReq::getMaterialName, row -> row, (existing, replacement) -> existing))
.collect(Collectors.toMap(MaterialImportRowReq::getMaterialName, row -> row, (existing,
replacement) -> existing))
.values()
.stream()
.toList();
@@ -284,8 +274,8 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
List<String> existName,
List<String> existCode) {
return list.stream()
.anyMatch(row -> EXIT.validate(req.getDuplicateName(), row.getMaterialName(), existName) || EXIT.validate(req
.getDuplicateCode(), row.getEncoding(), existCode));
.anyMatch(row -> EXIT.validate(req.getDuplicateName(), row.getMaterialName(), existName) || EXIT
.validate(req.getDuplicateCode(), row.getEncoding(), existCode));
}
/**
@@ -312,7 +302,9 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
* @param updateByNameList 修改用户
* @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)) {
baseMapper.insertBatch(insertList);
}
@@ -323,4 +315,102 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
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

@@ -133,7 +133,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
String phone = req.getPhone();
CheckUtils.throwIf(StrUtil.isNotBlank(phone) && this.isPhoneExists(phone, null), errorMsgTemplate, phone);
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
req.setPassword("{bcrypt}"+encoder.encode(req.getPassword()));
req.setPassword("{bcrypt}" + encoder.encode(req.getPassword()));
}
@Override

View File

@@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.*;
import lombok.RequiredArgsConstructor;
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.material.model.entity.MaterialInfoDO;
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.MaterialInfoResp;
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;
@@ -70,4 +68,15 @@ public class MaterialInfoController extends BaseController<MaterialInfoService,
public MaterialInfoImportResp importUser(@Validated @RequestBody MaterialInfoImportReq 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();
}
}

View File

@@ -93,7 +93,7 @@ public class UserController extends BaseController<UserService, UserResp, UserDe
ValidationUtils.throwIf(!ReUtil
.isMatch(RegexConstants.PASSWORD, rawNewPassword), "密码长度为 8-32 个字符,支持大小写字母、数字、特殊字符,至少包含字母和数字");
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
req.setNewPassword("{bcrypt}"+encoder.encode(rawNewPassword));
req.setNewPassword("{bcrypt}" + encoder.encode(rawNewPassword));
baseService.resetPassword(req, id);
}