package com.doumee.service.business.impl; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.doumee.biz.system.SystemDictDataBiz; import com.doumee.core.annotation.excel.ExcelEmbeddedImageReader; import com.doumee.core.annotation.excel.AsyncImportExcelSupport; import com.doumee.core.annotation.excel.ExcelImporter; import com.doumee.core.annotation.excel.RowEmbeddedImages; import com.doumee.core.constants.ResponseStatus; import com.doumee.core.exception.BusinessException; import com.doumee.core.model.LoginUserInfo; import com.doumee.core.model.PageData; import com.doumee.core.model.PageWrap; import com.doumee.core.utils.Constants; import com.doumee.core.utils.PinYinUtil; import com.doumee.core.utils.Utils; import com.doumee.dao.business.*; import com.doumee.dao.business.model.*; import com.doumee.dao.business.model.dto.OssModel; import com.doumee.service.CateParamSelectService; import com.doumee.service.business.BaseDataService; import com.doumee.service.business.GoodsImportTaskService; import org.apache.commons.lang3.StringUtils; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.shiro.SecurityUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigDecimal; import java.net.URLEncoder; import java.util.*; @Service public class GoodsImportTaskServiceImpl implements GoodsImportTaskService { private static final String[] ASYNC_TEMPLATE_HEADERS = { "商品文件夹名称", "商品名称", "商品类别", "商品品牌", "指导价", "入手价", "产品参数", "商品二级类别", "商品主图", "商品多图" }; /** 表头字体改为黑色的列:商品二级类别、商品主图、商品多图 */ private static final int[] BLACK_HEADER_COLS = {7, 8, 9}; /** 商品主图、商品多图列索引(与 GoodsImportAsync.index 一致) */ private static final int COL_MAIN_IMAGE = 8; private static final int COL_MULTI_IMAGE = 9; @Autowired private GoodsImportTaskMapper goodsImportTaskMapper; @Autowired private GoodsMapper goodsMapper; @Autowired private CategoryMapper categoryMapper; @Autowired private BrandMapper brandMapper; @Autowired private CateParamMapper cateParamMapper; @Autowired private GoodsParamMapper goodsParamMapper; @Autowired private MultifileMapper multifileMapper; @Autowired private BaseDataService baseDataService; @Autowired private SystemDictDataBiz systemDictDataBiz; @Autowired private CateParamSelectService cateParamSelectService; @Autowired @Lazy private GoodsImportTaskWorker goodsImportTaskWorker; @Override public void exportImportAsyncTemplate(HttpServletRequest request, HttpServletResponse response) throws Exception { LoginUserInfo loginUserInfo = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal(); String fileName = "产品异步导入表" + System.currentTimeMillis() + ".xlsx"; InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("static/file/goodsExcelTemp.xlsx"); Workbook workbook = new XSSFWorkbook(inputStream); Sheet sheet0 = workbook.getSheetAt(0); Row headerRow = sheet0.getRow(0); if (headerRow == null) { headerRow = sheet0.createRow(0); } for (int i = 0; i < ASYNC_TEMPLATE_HEADERS.length; i++) { Cell cell = headerRow.getCell(i); if (cell == null) { cell = headerRow.createCell(i); } cell.setCellValue(ASYNC_TEMPLATE_HEADERS[i]); } applyTemplateHeaderStyles(workbook, headerRow); applyTemplateColumnWidths(sheet0); CellStyle wrapStyle = workbook.createCellStyle(); wrapStyle.setWrapText(true); fillTemplateExampleRow(sheet0, wrapStyle); Sheet sheet = workbook.getSheetAt(1); CellStyle cs = workbook.createCellStyle(); cs.setWrapText(true); Row row = sheet.createRow(1); List categoryList = categoryMapper.selectList( new QueryWrapper().eq("ISDELETED", 0).eq("COMPANY_ID", loginUserInfo.getCompanyId())); categoryList.forEach(i -> i.setParamList( cateParamMapper.selectList(new QueryWrapper().eq("ISDELETED", 0).eq("CATEGORY_ID", i.getId())))); if (categoryList != null) { for (int i = 0; i < categoryList.size(); i++) { Category category = categoryList.get(i); row = sheet.createRow(i + 1); row.createCell(0).setCellValue(category.getName()); List paramList = category.getParamList(); StringBuilder param = new StringBuilder(); if (paramList != null) { for (int j = 0; j < paramList.size(); j++) { if (j > 0) { param.append("\r\n"); } param.append(paramList.get(j).getName()).append(":"); } } Cell cell = row.createCell(1); cell.setCellStyle(cs); cell.setCellValue(param.toString()); } } setResponseHeader(response, fileName); OutputStream os = response.getOutputStream(); workbook.write(os); os.flush(); os.close(); workbook.close(); } @Override @Transactional(rollbackFor = Exception.class) public Integer submitTask(MultipartFile file) { LoginUserInfo user = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal(); if (hasUnfinishedTask(user.getCompanyId())) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "当前存在未完成的导入任务,请等待完成后再提交"); } if (file == null || file.isEmpty()) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "导入文件不能为空"); } String originalName = sanitizeImportFileName(StringUtils.defaultString(file.getOriginalFilename(), "import.xlsx")); String saveDir = buildImportDir(user.getCompanyId()); File dir = new File(saveDir); if (!dir.exists() && !dir.mkdirs()) { throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), "创建导入目录失败"); } String saveName = System.currentTimeMillis() + "_" + originalName; File saved = new File(dir, saveName); try { saveImportFile(file, saved); } catch (Exception e) { throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), "保存导入文件失败:" + e.getMessage()); } GoodsImportTask task = new GoodsImportTask(); task.setCompanyId(user.getCompanyId()); task.setCreator(user.getId()); task.setCreateDate(new Date()); task.setEditDate(new Date()); task.setIsdeleted(Constants.ZERO); task.setFileName(originalName); task.setFilePath(saved.getAbsolutePath()); task.setStatus(GoodsImportTask.STATUS_PENDING); task.setTotalRows(0); task.setSuccessRows(0); task.setFailedRows(0); task.setProgress(0); goodsImportTaskMapper.insert(task); goodsImportTaskWorker.runTask(task.getId()); return task.getId(); } @Override public void processTask(Integer taskId) { GoodsImportTask task = goodsImportTaskMapper.selectById(taskId); if (task == null || !Constants.equalsInteger(Constants.ZERO, task.getIsdeleted())) { return; } task.setStatus(GoodsImportTask.STATUS_PROCESSING); task.setEditDate(new Date()); goodsImportTaskMapper.updateById(task); List errorLines = new ArrayList<>(); int success = 0; int failed = 0; int rowNum = 1; try { ExcelImporter ie = new ExcelImporter(new File(task.getFilePath()), 0, 0); List dataList = ie.getDataList(GoodsImportAsync.class, null); if (dataList == null || dataList.isEmpty()) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "录入数据为空"); } AsyncImportExcelSupport.ColumnMapping columnMapping = AsyncImportExcelSupport.readAndApplyTextFields(task.getFilePath(), 0, 0, dataList); task.setTotalRows(dataList.size()); goodsImportTaskMapper.updateById(task); Map rowImageMap = ExcelEmbeddedImageReader.read( task.getFilePath(), 0, 0, columnMapping.getMainImageCol(), columnMapping.getMultiImageCol()); LoginUserInfo user = new LoginUserInfo(); user.setId(task.getCreator()); user.setCompanyId(task.getCompanyId()); List brandList = brandMapper.selectList(new LambdaQueryWrapper().eq(Brand::getIsdeleted, Constants.ZERO) .and(w -> w.eq(Brand::getCompanyId, task.getCompanyId()).or().eq(Brand::getType, Constants.ONE))); List goodsList = goodsMapper.selectList(new LambdaQueryWrapper().eq(Goods::getIsdeleted, Constants.ZERO) .eq(Goods::getCompanyId, task.getCompanyId())); List categoryList = categoryMapper.selectList(new LambdaQueryWrapper().eq(Category::getIsdeleted, Constants.ZERO) .eq(Category::getCompanyId, task.getCompanyId())); Map> subCategoryMap = buildSubCategoryMap(task.getCompanyId()); List allParamsList = cateParamMapper.selectList(new LambdaQueryWrapper().eq(CateParam::getIsdeleted, Constants.ZERO)); OssModel ossModel = baseDataService.initOssModel(); int processed = 0; int dataStartRow = 1; for (int i = 0; i < dataList.size(); i++) { GoodsImportAsync row = dataList.get(i); int excelRowIndex = dataStartRow + i; rowNum = excelRowIndex + 1; if (isBlankRow(row)) { processed++; updateProgress(task, processed, success, failed); continue; } RowEmbeddedImages rowImages = rowImageMap.getOrDefault(excelRowIndex, RowEmbeddedImages.empty()); try { importOneRow(row, rowNum, user, rowImages, brandList, goodsList, categoryList, subCategoryMap, allParamsList, ossModel); success++; } catch (BusinessException e) { failed++; errorLines.add("第【" + rowNum + "】行:" + e.getMessage()); } catch (Exception e) { failed++; errorLines.add("第【" + rowNum + "】行:导入异常"); } processed++; updateProgress(task, processed, success, failed); } task.setStatus(GoodsImportTask.STATUS_SUCCESS); task.setSuccessRows(success); task.setFailedRows(failed); task.setProgress(100); if (!errorLines.isEmpty()) { task.setErrorMessage("部分行导入失败,共 " + failed + " 行"); task.setErrorDetail(JSON.toJSONString(errorLines)); if (success == 0) { task.setStatus(GoodsImportTask.STATUS_FAILED); task.setErrorMessage("全部导入失败"); } } cateParamSelectService.dealCateParamSelect(); } catch (BusinessException e) { task.setStatus(GoodsImportTask.STATUS_FAILED); task.setErrorMessage(e.getMessage()); task.setFailedRows(Math.max(failed, 1)); } catch (Exception e) { task.setStatus(GoodsImportTask.STATUS_FAILED); task.setErrorMessage("导入任务执行异常,请稍后重试"); if (!errorLines.isEmpty()) { task.setErrorDetail(JSON.toJSONString(errorLines)); } } finally { task.setEditDate(new Date()); goodsImportTaskMapper.updateById(task); } } private void importOneRow(GoodsImportAsync m, int num, LoginUserInfo user, RowEmbeddedImages rowImages, List brandList, List goodsList, List categoryList, Map> subCategoryMap, List allParamsList, OssModel ossModel) { if (StringUtils.isBlank(m.getName()) || StringUtils.isBlank(m.getCategory()) || StringUtils.isBlank(m.getBrand()) || StringUtils.isBlank(m.getZdPrice()) || StringUtils.isBlank(m.getPrice())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "数据无效,请检查名称/类别/品牌/价格"); } if (StringUtils.isNotBlank(m.getId())) { Goods existsFolder = new Goods(); existsFolder.setRemark(m.getId()); existsFolder.setCompanyId(user.getCompanyId()); existsFolder.setIsdeleted(Constants.ZERO); existsFolder = goodsMapper.selectOne(new QueryWrapper<>(existsFolder).last(" limit 1")); if (existsFolder != null) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "商品图片文件夹名称重复"); } } Category cate = getTopCategoryByName(m.getCategory(), categoryList); if (cate == null) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "商品类别无效"); } Category subCate = null; String subCategoryName = normalizeCategoryName(m.getSubCategory()); if (StringUtils.isNotBlank(subCategoryName)) { subCate = matchSubCategoryUnderParent(cate, subCategoryName, subCategoryMap, user.getCompanyId()); if (subCate == null) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "商品二级类别【" + subCategoryName + "】在一级类别【" + normalizeCategoryName(cate.getName()) + "】的子类别中不存在"); } } Brand brand = getBrandByName(m.getBrand(), brandList); if (brand == null) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "商品品牌无效"); } if (findGoodsByName(m.getName(), goodsList) != null) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "产品名称【" + m.getName() + "】已存在"); } Goods newModel = new Goods(); if (StringUtils.isNotBlank(m.getId())) { newModel.setRemark(m.getId()); } newModel.setCategoryId(cate.getId()); if (subCate != null) { newModel.setSubCategoryId(subCate.getId()); } newModel.setBrandId(brand.getId()); newModel.setIsdeleted(Constants.ZERO); newModel.setCreator(user.getId()); newModel.setCompanyId(user.getCompanyId()); newModel.setCreateDate(new Date()); newModel.setName(m.getName().trim()); newModel.setStatus(Constants.ZERO); try { newModel.setZdPrice(BigDecimal.valueOf(Double.parseDouble(m.getZdPrice().trim()))); newModel.setPrice(BigDecimal.valueOf(Double.parseDouble(m.getPrice().trim()))); } catch (Exception e) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "指导价或入手价无效"); } newModel.setPinyin(PinYinUtil.getFullSpell(newModel.getName())); newModel.setShortPinyin(PinYinUtil.getFirstSpell(newModel.getName())); String goodsFolder = ossModel.getGoodsFolder(); String mainImg = null; if (rowImages.getMainImage() != null) { mainImg = uploadEmbeddedImage(ossModel, goodsFolder, rowImages.getMainImage()); } List multiUrls = new ArrayList<>(); if (rowImages.getMultiImages() != null) { for (RowEmbeddedImages.EmbeddedImageItem item : rowImages.getMultiImages()) { String url = uploadEmbeddedImage(ossModel, goodsFolder, item); if (StringUtils.isNotBlank(url)) { multiUrls.add(url); } } } if (StringUtils.isNotBlank(mainImg)) { newModel.setImgurl(mainImg); } goodsMapper.insert(newModel); goodsList.add(newModel); dealGoodsParam(cate, newModel, m.getParamStr(), allParamsList, num); dealMultifiles(multiUrls, newModel); } @Override public PageData findPage(PageWrap pageWrap) { LoginUserInfo user = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal(); IPage page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity()); QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("COMPANY_ID", user.getCompanyId()); wrapper.eq("ISDELETED", Constants.ZERO); GoodsImportTask model = pageWrap.getModel(); if (model != null) { if (model.getStatus() != null) { wrapper.eq("STATUS", model.getStatus()); } if (model.getCreateDateStart() != null) { wrapper.ge("CREATE_DATE", model.getCreateDateStart()); } if (model.getCreateDateEnd() != null) { wrapper.le("CREATE_DATE", model.getCreateDateEnd()); } } wrapper.orderByDesc("ID"); IPage result = goodsImportTaskMapper.selectPage(page, wrapper); if (result.getRecords() != null) { result.getRecords().forEach(this::fillStatusText); } return PageData.from(result); } @Override public GoodsImportTask findById(Integer id) { LoginUserInfo user = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal(); GoodsImportTask task = goodsImportTaskMapper.selectById(id); if (task == null || !Constants.equalsInteger(Constants.ZERO, task.getIsdeleted()) || !user.getCompanyId().equals(task.getCompanyId())) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "任务不存在"); } fillStatusText(task); return task; } @Override public boolean hasUnfinishedTask(Integer companyId) { Long count = goodsImportTaskMapper.selectCount(new LambdaQueryWrapper() .eq(GoodsImportTask::getCompanyId, companyId) .eq(GoodsImportTask::getIsdeleted, Constants.ZERO) .in(GoodsImportTask::getStatus, GoodsImportTask.STATUS_PENDING, GoodsImportTask.STATUS_PROCESSING)); return count != null && count > 0; } @Override @Transactional(rollbackFor = Exception.class) public void deleteById(Integer id) { LoginUserInfo user = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal(); GoodsImportTask task = goodsImportTaskMapper.selectById(id); if (task == null || !Constants.equalsInteger(Constants.ZERO, task.getIsdeleted()) || !user.getCompanyId().equals(task.getCompanyId())) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "任务不存在"); } if (Constants.equalsInteger(GoodsImportTask.STATUS_PENDING, task.getStatus()) || Constants.equalsInteger(GoodsImportTask.STATUS_PROCESSING, task.getStatus())) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "待处理或处理中的任务无法删除"); } task.setIsdeleted(Constants.ONE); task.setEditor(user.getId()); task.setEditDate(new Date()); goodsImportTaskMapper.updateById(task); removeTaskFile(task.getFilePath()); } private void removeTaskFile(String filePath) { if (StringUtils.isBlank(filePath)) { return; } try { File file = new File(filePath); if (file.isFile()) { file.delete(); } } catch (Exception ignored) { } } private void updateProgress(GoodsImportTask task, int processed, int success, int failed) { int total = Math.max(task.getTotalRows(), 1); task.setProgress(Math.min(100, (int) (processed * 100.0 / total))); task.setSuccessRows(success); task.setFailedRows(failed); task.setEditDate(new Date()); goodsImportTaskMapper.updateById(task); } private void fillStatusText(GoodsImportTask task) { if (task == null || task.getStatus() == null) { return; } switch (task.getStatus()) { case GoodsImportTask.STATUS_PENDING: task.setStatusText("待处理"); break; case GoodsImportTask.STATUS_PROCESSING: task.setStatusText("处理中"); break; case GoodsImportTask.STATUS_SUCCESS: task.setStatusText("成功"); break; case GoodsImportTask.STATUS_FAILED: task.setStatusText("失败"); break; default: task.setStatusText("未知"); } } private boolean isBlankRow(GoodsImportAsync row) { return StringUtils.isBlank(row.getName()) && StringUtils.isBlank(row.getCategory()) && StringUtils.isBlank(row.getBrand()) && StringUtils.isBlank(row.getZdPrice()) && StringUtils.isBlank(row.getPrice()) && StringUtils.isBlank(row.getId()); } private String buildImportDir(Integer companyId) { String goodsDir = buildImportDirByBase(companyId, Constants.GOODS_IMG_DIR); if (canCreateDir(goodsDir)) { return goodsDir; } String fileDir = buildImportDirByBase(companyId, Constants.FILE_DIR); if (canCreateDir(fileDir)) { return fileDir; } String tmpDir = normalizeDirPath(System.getProperty("java.io.tmpdir")) + "preselect" + File.separator + "import_tasks" + File.separator + companyId; if (!canCreateDir(tmpDir)) { throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), "创建导入目录失败"); } return tmpDir; } private String buildImportDirByBase(Integer companyId, String baseKey) { String base = getDictCode(baseKey); if (StringUtils.isBlank(base)) { return ""; } String proDir = getDictCode(Constants.PROJECTS); return normalizeDirPath(base) + normalizeDirPath(proDir) + companyId + File.separator + "import_tasks"; } private String getDictCode(String label) { try { com.doumee.dao.system.model.SystemDictData data = systemDictDataBiz.queryByCode(Constants.SYSTEM, label); return data == null ? "" : StringUtils.defaultString(data.getCode(), ""); } catch (Exception e) { return ""; } } private String normalizeDirPath(String path) { if (StringUtils.isBlank(path)) { return ""; } String normalized = path.replace('/', File.separatorChar).replace('\\', File.separatorChar); if (!normalized.endsWith(File.separator)) { normalized += File.separator; } return normalized; } private boolean canCreateDir(String dirPath) { if (StringUtils.isBlank(dirPath)) { return false; } File dir = new File(dirPath); return dir.exists() || dir.mkdirs(); } private String sanitizeImportFileName(String fileName) { String name = fileName.replace("\\", "_").replace("/", "_").replace(":", "_"); if (!name.toLowerCase(Locale.ROOT).endsWith(".xlsx") && !name.toLowerCase(Locale.ROOT).endsWith(".xls")) { name = name + ".xlsx"; } return name; } private void saveImportFile(MultipartFile file, File dest) throws IOException { File target = dest.getAbsoluteFile(); File parent = target.getParentFile(); if (parent != null && !parent.exists() && !parent.mkdirs()) { throw new IOException("无法创建目录: " + parent.getAbsolutePath()); } if (target.exists() && !target.delete()) { throw new IOException("无法覆盖已存在文件: " + target.getAbsolutePath()); } try (InputStream in = file.getInputStream(); FileOutputStream out = new FileOutputStream(target)) { byte[] buffer = new byte[8192]; int len; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } out.flush(); } } private String uploadEmbeddedImage(OssModel ossModel, String folder, RowEmbeddedImages.EmbeddedImageItem item) { if (item == null || item.getData() == null || item.getData().length == 0) { return null; } String ext = StringUtils.defaultIfBlank(item.getExtension(), ".jpg"); File temp = null; try { temp = File.createTempFile("goods_import_img_", ext); try (FileOutputStream out = new FileOutputStream(temp)) { out.write(item.getData()); } return baseDataService.getOssImgurl(ossModel, folder, temp); } catch (Exception e) { return null; } finally { if (temp != null && temp.exists() && !temp.delete()) { temp.deleteOnExit(); } } } private void dealMultifiles(List urls, Goods goods) { if (urls == null || urls.isEmpty()) { return; } List list = new ArrayList<>(); for (int i = 0; i < urls.size(); i++) { Multifile f = new Multifile(); f.setObjType(Constants.ZERO); f.setName(urls.get(i)); f.setType(Constants.ZERO); f.setObjId(goods.getId()); f.setCreateDate(goods.getCreateDate()); f.setCreator(goods.getCreator()); f.setIsdeleted(Constants.ZERO); f.setSortnum(i + Constants.ONE); f.setCompanyId(goods.getCompanyId()); f.setFileurl(urls.get(i)); list.add(f); } multifileMapper.insertBatch(list); } private void dealGoodsParam(Category cate, Goods goods, String paramStr, List allParamsList, int num) { List paramList = new ArrayList<>(); String[] params = StringUtils.defaultString(paramStr, "").split("\n"); List cateParams = getCateParamByCateId(cate.getId(), allParamsList); for (String s : params) { if (StringUtils.isBlank(s)) { continue; } s = s.replace(":", ":"); String[] ts = s.split(":"); if (ts.length == 0) { continue; } CateParam ta = getCateParamByName(ts[0], cateParams); if (ta == null) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "产品参数中【" + s + "】无效"); } GoodsParam gp = new GoodsParam(); gp.setIsdeleted(Constants.ZERO); gp.setCreator(goods.getCreator()); gp.setCreateDate(goods.getCreateDate()); gp.setGoodsId(goods.getId()); gp.setName(ta.getName()); gp.setPramaId(ta.getId()); gp.setStatus(Constants.ZERO); gp.setVal(ts.length > 1 ? ts[1] : null); gp.setSortnum(ta.getSortnum()); paramList.add(gp); } if (!paramList.isEmpty()) { goodsParamMapper.insertBatch(paramList); } } private String normalizeCategoryName(String name) { if (name == null) { return ""; } return name.trim().replace('\u00A0', ' ').replaceAll("\\s+", " "); } private boolean isTopLevelCategory(Category category) { return category != null && (category.getParentId() == null || Constants.equalsInteger(category.getParentId(), Constants.ZERO)); } private Category getTopCategoryByName(String name, List list) { if (list == null) { return null; } String normalized = normalizeCategoryName(name); if (StringUtils.isBlank(normalized)) { return null; } for (Category c : list) { if (isTopLevelCategory(c) && StringUtils.equals(normalized, normalizeCategoryName(c.getName()))) { return c; } } return null; } /** 按企业加载一级类别 -> 子类别列表 */ private Map> buildSubCategoryMap(Integer companyId) { List subCategories = categoryMapper.selectList(new LambdaQueryWrapper() .eq(Category::getCompanyId, companyId) .eq(Category::getIsdeleted, Constants.ZERO) .isNotNull(Category::getParentId) .orderByAsc(Category::getSortnum)); Map> map = new HashMap<>(); for (Category sub : subCategories) { if (sub.getParentId() == null || Constants.equalsInteger(sub.getParentId(), Constants.ZERO)) { continue; } map.computeIfAbsent(sub.getParentId(), k -> new ArrayList<>()).add(sub); } return map; } /** * 根据本行已匹配的一级【商品类别】,在其子类别中查找与【商品二级类别】名称一致的记录。 */ private Category matchSubCategoryUnderParent(Category topCategory, String subCategoryName, Map> subCategoryMap, Integer companyId) { if (topCategory == null || topCategory.getId() == null) { return null; } String normalized = normalizeCategoryName(subCategoryName); if (StringUtils.isBlank(normalized)) { return null; } List children = subCategoryMap.get(topCategory.getId()); if (children == null || children.isEmpty()) { Integer cid = topCategory.getCompanyId() != null ? topCategory.getCompanyId() : companyId; children = categoryMapper.selectList(new LambdaQueryWrapper() .eq(Category::getParentId, topCategory.getId()) .eq(Category::getCompanyId, cid) .eq(Category::getIsdeleted, Constants.ZERO) .orderByAsc(Category::getSortnum)); } for (Category child : children) { if (StringUtils.equals(normalized, normalizeCategoryName(child.getName()))) { return child; } } return null; } private Brand getBrandByName(String name, List list) { if (list == null) { return null; } for (Brand b : list) { if (StringUtils.equals(name.trim(), b.getName())) { return b; } } return null; } private Goods findGoodsByName(String name, List list) { if (list == null) { return null; } for (Goods g : list) { if (StringUtils.equals(name.trim(), g.getName())) { return g; } } return null; } private List getCateParamByCateId(Integer cateId, List all) { List list = new ArrayList<>(); if (all == null) { return list; } for (CateParam p : all) { if (Constants.equalsInteger(cateId, p.getCategoryId())) { list.add(p); } } return list; } private CateParam getCateParamByName(String name, List params) { if (params == null) { return null; } for (CateParam p : params) { if (StringUtils.equals(name.trim(), p.getName())) { return p; } } return null; } /** 原模板「产品参数」列宽(字符宽),用于新版 G 列放大 */ private static final double TEMPLATE_PARAM_COL_WIDTH = 16.33203125D; private void applyTemplateHeaderStyles(Workbook workbook, Row headerRow) { CellStyle evenStyle = null; CellStyle oddStyle = null; for (int i = 0; i < 6; i++) { Cell cell = headerRow.getCell(i); if (cell == null) { continue; } if (i % 2 == 0) { evenStyle = cell.getCellStyle(); } else { oddStyle = cell.getCellStyle(); } } if (evenStyle == null && headerRow.getCell(0) != null) { evenStyle = headerRow.getCell(0).getCellStyle(); } if (oddStyle == null) { oddStyle = evenStyle; } for (int i = 0; i < ASYNC_TEMPLATE_HEADERS.length; i++) { Cell cell = headerRow.getCell(i); if (cell == null) { cell = headerRow.createCell(i); } CellStyle style = (i % 2 == 0) ? evenStyle : oddStyle; if (style != null) { cell.setCellStyle(style); } } for (int col : BLACK_HEADER_COLS) { Cell cell = headerRow.getCell(col); if (cell == null) { continue; } CellStyle baseStyle = cell.getCellStyle(); CellStyle blackStyle = workbook.createCellStyle(); if (baseStyle != null) { blackStyle.cloneStyleFrom(baseStyle); } Font baseFont = baseStyle != null ? workbook.getFontAt(baseStyle.getFontIndexAsInt()) : null; Font blackFont = workbook.createFont(); if (baseFont != null) { blackFont.setBold(baseFont.getBold()); blackFont.setFontHeight(baseFont.getFontHeight()); blackFont.setFontName(baseFont.getFontName()); } blackFont.setColor(IndexedColors.BLACK.getIndex()); blackStyle.setFont(blackFont); cell.setCellStyle(blackStyle); } } private void applyTemplateColumnWidths(Sheet sheet) { int paramColIndex = 6; int doubledWidth = (int) Math.round(TEMPLATE_PARAM_COL_WIDTH * 256 * 2); sheet.setColumnWidth(paramColIndex, doubledWidth); } private void fillTemplateExampleRow(Sheet sheet0, CellStyle wrapStyle) { Row exampleRow = sheet0.getRow(1); if (exampleRow == null) { exampleRow = sheet0.createRow(1); } exampleRow.createCell(0).setCellValue("125"); exampleRow.createCell(1).setCellValue("美的电烤箱"); exampleRow.createCell(2).setCellValue("电烤箱"); exampleRow.createCell(3).setCellValue("美的"); exampleRow.createCell(4).setCellValue("2888"); exampleRow.createCell(5).setCellValue("2300"); Cell paramCell = exampleRow.createCell(6); paramCell.setCellStyle(wrapStyle); paramCell.setCellValue("容量:99升\r\n重量:88KG\r\n型号:77"); exampleRow.createCell(7).setCellValue(""); exampleRow.createCell(8).setCellValue(""); exampleRow.createCell(9).setCellValue(""); } private void setResponseHeader(HttpServletResponse response, String fileName) throws Exception { String encodeFileName = URLEncoder.encode(fileName, "UTF-8"); response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment;filename=" + encodeFileName); response.setHeader("eva-opera-type", "download"); response.setHeader("eva-download-filename", encodeFileName); } }