diff --git a/wms-module-system/pom.xml b/wms-module-system/pom.xml index 64845da..05be82f 100644 --- a/wms-module-system/pom.xml +++ b/wms-module-system/pom.xml @@ -19,4 +19,4 @@ wms-common - \ No newline at end of file + diff --git a/wms-module-system/src/main/java/top/wms/admin/material/mapper/MaterialInfoMapper.java b/wms-module-system/src/main/java/top/wms/admin/material/mapper/MaterialInfoMapper.java index dd54d25..75ed192 100644 --- a/wms-module-system/src/main/java/top/wms/admin/material/mapper/MaterialInfoMapper.java +++ b/wms-module-system/src/main/java/top/wms/admin/material/mapper/MaterialInfoMapper.java @@ -15,5 +15,6 @@ import java.util.List; @Repository public interface MaterialInfoMapper extends BaseMapper { public int updateByName(List list); + public int updateByCode(List list); } diff --git a/wms-module-system/src/main/java/top/wms/admin/material/model/resp/MaterialInfoImportResp.java b/wms-module-system/src/main/java/top/wms/admin/material/model/resp/MaterialInfoImportResp.java index 0ac98e8..37a5eca 100644 --- a/wms-module-system/src/main/java/top/wms/admin/material/model/resp/MaterialInfoImportResp.java +++ b/wms-module-system/src/main/java/top/wms/admin/material/model/resp/MaterialInfoImportResp.java @@ -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; diff --git a/wms-module-system/src/main/java/top/wms/admin/material/service/MaterialInfoService.java b/wms-module-system/src/main/java/top/wms/admin/material/service/MaterialInfoService.java index 1c394cf..6644972 100644 --- a/wms-module-system/src/main/java/top/wms/admin/material/service/MaterialInfoService.java +++ b/wms-module-system/src/main/java/top/wms/admin/material/service/MaterialInfoService.java @@ -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; @@ -24,33 +21,38 @@ import java.io.IOException; */ public interface MaterialInfoService extends BaseService { - /* - * - * 根据编码查询物料信息 - * */ - public MaterialInfoDO getMaterialInfoByCode(String code); + /* + * + * 根据编码查询物料信息 + * */ + public MaterialInfoDO getMaterialInfoByCode(String code); - /** - * 下载导入模板 - * - * @param response 响应对象 - * @throws IOException / - */ - void downloadImportTemplate(HttpServletResponse response) throws IOException; + /** + * 下载导入模板 + * + * @param response 响应对象 + * @throws IOException / + */ + void downloadImportTemplate(HttpServletResponse response) throws IOException; - /** - * 解析导入数据 - * - * @param file 导入文件 - * @return 解析结果 - */ - MaterialImportParseResp parseImport(MultipartFile file); + /** + * 解析导入数据 + * + * @param file 导入文件 + * @return 解析结果 + */ + MaterialImportParseResp parseImport(MultipartFile file); - /** - * 导入数据 - * - * @param req 导入信息 - * @return 导入结果 - */ - MaterialInfoImportResp importMaterial(MaterialInfoImportReq req); + /** + * 导入数据 + * + * @param req 导入信息 + * @return 导入结果 + */ + MaterialInfoImportResp importMaterial(MaterialInfoImportReq req); + + /* + * 照片批量上传处理 + * */ + void uploadMaterialPhotos(MultipartFile file); } diff --git a/wms-module-system/src/main/java/top/wms/admin/material/service/impl/MaterialInfoServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/material/service/impl/MaterialInfoServiceImpl.java index 2af22fb..cfdccdd 100644 --- a/wms-module-system/src/main/java/top/wms/admin/material/service/impl/MaterialInfoServiceImpl.java +++ b/wms-module-system/src/main/java/top/wms/admin/material/service/impl/MaterialInfoServiceImpl.java @@ -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 implements MaterialInfoService { + private static final Set 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 seenCode = new HashSet<>(); boolean hasDuplicateEncoding = validRowList.stream() - .map(MaterialImportRowReq::getEncoding) - .anyMatch(encoding -> encoding != null && !seenCode.add(encoding)); + .map(MaterialImportRowReq::getEncoding) + .anyMatch(encoding -> encoding != null && !seenCode.add(encoding)); CheckUtils.throwIf(hasDuplicateEncoding, "存在重复物料编码,请检测数据"); // 查询重复用户 materialImportResp - .setDuplicateNameRows(countExistByField(validRowList, MaterialImportRowReq::getMaterialName, MaterialInfoDO::getMaterialName, false)); + .setDuplicateNameRows(countExistByField(validRowList, MaterialImportRowReq::getMaterialName, MaterialInfoDO::getMaterialName, false)); // 查询重复邮箱 materialImportResp - .setDuplicateCodeRows(countExistByField(validRowList, MaterialImportRowReq::getEncoding, MaterialInfoDO::getEncoding, false)); + .setDuplicateCodeRows(countExistByField(validRowList, MaterialImportRowReq::getEncoding, MaterialInfoDO::getEncoding, false)); // 设置导入会话并缓存数据,有效期10分钟 String importKey = UUID.fastUUID().toString(true); RedisUtils.set(CacheConstants.DATA_IMPORT_KEY + importKey, JSONUtil.toJsonStr(validRowList), Duration - .ofMinutes(10)); + .ofMinutes(10)); materialImportResp.setImportKey(importKey); return materialImportResp; } - @Override @Transactional(rollbackFor = Exception.class) public MaterialInfoImportResp importMaterial(MaterialInfoImportReq req) { @@ -160,22 +156,21 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl existName = listExistByField(importMaterialList, MaterialImportRowReq::getMaterialName, MaterialInfoDO::getMaterialName); List existCode = listExistByField(importMaterialList, MaterialImportRowReq::getEncoding, MaterialInfoDO::getEncoding); - CheckUtils - .throwIf(isExitImportMaterial(req, importMaterialList, existName, existCode), "数据不符合导入策略,已退出导入"); + CheckUtils.throwIf(isExitImportMaterial(req, importMaterialList, existName, existCode), "数据不符合导入策略,已退出导入"); // 批量操作数据库集合 List insertList = new ArrayList<>(); List updateByNameList = new ArrayList<>(); List 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 materialRowList, @@ -227,10 +217,9 @@ public class MaterialInfoServiceImpl extends BaseServiceImpllambdaQuery() - .in(dbField, fieldEncrypt ? SecureUtils.encryptFieldByAes(fieldValues) : fieldValues)); + .in(dbField, fieldEncrypt ? SecureUtils.encryptFieldByAes(fieldValues) : fieldValues)); } - /** * 过滤无效的导入用户数据(批量导入不严格校验数据) * @@ -239,22 +228,23 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl filterImportData(List importRowList) { // 校验过滤 List list = importRowList.stream() - .filter(row -> ValidationUtil.validate(row).isEmpty()) - .toList(); + .filter(row -> ValidationUtil.validate(row).isEmpty()) + .toList(); // 物料名去重 return list.stream() - .collect(Collectors.toMap(MaterialImportRowReq::getMaterialName, row -> row, (existing, replacement) -> existing)) - .values() - .stream() - .toList(); + .collect(Collectors.toMap(MaterialImportRowReq::getMaterialName, row -> row, (existing, + replacement) -> existing)) + .values() + .stream() + .toList(); } /** * 按指定数据集获取数据库已存在内容 * * @param materialRowList 导入的数据源 - * @param rowField 导入数据的字段 - * @param dbField 对比数据库的字段 + * @param rowField 导入数据的字段 + * @param dbField 对比数据库的字段 * @return 存在的内容 */ private List listExistByField(List materialRowList, @@ -265,54 +255,56 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl materialDOList = baseMapper.selectList(Wrappers.lambdaQuery() - .in(dbField, fieldValues) - .select(dbField)); + .in(dbField, fieldValues) + .select(dbField)); return materialDOList.stream().map(dbField).filter(Objects::nonNull).toList(); } /** * 判断是否退出导入 * - * @param req 导入参数 - * @param list 导入数据 - * @param existName 导入数据中已存在的物料名 - * @param existCode 导入数据中已存在的物料编号 + * @param req 导入参数 + * @param list 导入数据 + * @param existName 导入数据中已存在的物料名 + * @param existCode 导入数据中已存在的物料编号 * @return 是否退出 */ private boolean isExitImportMaterial(MaterialInfoImportReq req, - List list, - List existName, - List existCode) { + List list, + List existName, + List 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)); } /** * 判断是否跳过导入 * - * @param req 导入参数 - * @param row 导入数据 - * @param existName 导入数据中已存在的物料名称 - * @param existCode 导入数据中已存在的物料编号 + * @param req 导入参数 + * @param row 导入数据 + * @param existName 导入数据中已存在的物料名称 + * @param existCode 导入数据中已存在的物料编号 * @return 是否跳过 */ private boolean isSkipMaterialImport(MaterialInfoImportReq req, - MaterialImportRowReq row, - List existName, - List existCode) { + MaterialImportRowReq row, + List existName, + List existCode) { return SKIP.validate(req.getDuplicateName(), row.getMaterialName(), existName) || SKIP.validate(req - .getDuplicateCode(), row.getEncoding(), existCode); + .getDuplicateCode(), row.getEncoding(), existCode); } /** * 导入用户 * - * @param insertList 新增用户 - * @param updateByNameList 修改用户 - * @param updateByCodeList 用户角色关联 + * @param insertList 新增用户 + * @param updateByNameList 修改用户 + * @param updateByCodeList 用户角色关联 */ - private void doImportMaterial(List insertList, List updateByNameList, List updateByCodeList) { + private void doImportMaterial(List insertList, + List updateByNameList, + List updateByCodeList) { if (CollUtil.isNotEmpty(insertList)) { baseMapper.insertBatch(insertList); } @@ -323,4 +315,102 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl 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 codeUrlMap) { + CheckUtils.throwIfEmpty(codeUrlMap, "照片为空,请重新上传"); + List existList = baseMapper.selectList(Wrappers.lambdaQuery() + .in(MaterialInfoDO::getEncoding, codeUrlMap.keySet())); + if (existList.isEmpty()) { + log.warn("未找到任何匹配的物料编码"); + return; + } + List 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 existCodes = existList.stream().map(MaterialInfoDO::getEncoding).collect(Collectors.toSet()); + + codeUrlMap.keySet() + .stream() + .filter(code -> !existCodes.contains(code)) + .forEach(code -> log.warn("物料编码 [{}] 不存在,照片更新失败", code)); + } + } diff --git a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/UserServiceImpl.java b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/UserServiceImpl.java index 35fe50c..fcef782 100644 --- a/wms-module-system/src/main/java/top/wms/admin/system/service/impl/UserServiceImpl.java +++ b/wms-module-system/src/main/java/top/wms/admin/system/service/impl/UserServiceImpl.java @@ -133,7 +133,7 @@ public class UserServiceImpl extends BaseServiceImpl { @GetMapping("/code/{code}") - public MaterialInfoDO getMaterialInfoByCode(@PathVariable String code) { - return baseService.getMaterialInfoByCode(code); + public MaterialInfoDO getMaterialInfoByCode(@PathVariable String code) { + return baseService.getMaterialInfoByCode(code); } @Operation(summary = "下载导入模板", description = "下载导入模板") @@ -70,4 +68,15 @@ public class MaterialInfoController extends BaseController originalFilename == null || !originalFilename.endsWith(".zip"), "仅支持上传ZIP格式文件"); + baseService.uploadMaterialPhotos(zipFile); + return R.ok(); + } } diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/system/UserController.java b/wms-webapi/src/main/java/top/wms/admin/controller/system/UserController.java index 286b3e5..edd39bd 100644 --- a/wms-webapi/src/main/java/top/wms/admin/controller/system/UserController.java +++ b/wms-webapi/src/main/java/top/wms/admin/controller/system/UserController.java @@ -93,7 +93,7 @@ public class UserController extends BaseController