@@ -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 ) ) ;
}
}
}