feat(goods): 添加Excel批量导入商品功能
- 集成POI库支持Excel文件读取和解析 - 实现商品信息从Excel表格到数据库的批量导入 - 支持商品分类自动创建和映射 - 添加商品图片从Excel单元格提取和保存功能 - 实现导入过程中的数据验证和错误处理 - 提供导入结果统计和反馈信息 - 支持跳过已存在商品的可选配置 - 添加导入参数的灵活配置选项
This commit is contained in:
86
.idea/workspace.xml
generated
Normal file
86
.idea/workspace.xml
generated
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="3ac55b60-f0f7-4a9b-af40-ce3c8e3c4479" name="更改" comment="">
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/main/java/com/gxwebsoft/shop/controller/GoodsController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/gxwebsoft/shop/controller/GoodsController.java" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo"><![CDATA[{
|
||||||
|
"associatedIndex": 6
|
||||||
|
}]]></component>
|
||||||
|
<component name="ProjectId" id="37VTGX36DC5AWDQalQxF4yPXvre" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||||
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
|
"git-widget-placeholder": "dev",
|
||||||
|
"kotlin-language-version-configured": "true",
|
||||||
|
"project.structure.last.edited": "项目",
|
||||||
|
"project.structure.proportion": "0.0",
|
||||||
|
"project.structure.side.proportion": "0.0",
|
||||||
|
"应用程序.WebSoftApplication.executor": "Run"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="RunManager">
|
||||||
|
<configuration name="WebSoftApplication" type="Application" factoryName="Application" temporary="true" nameIsGenerated="true">
|
||||||
|
<option name="MAIN_CLASS_NAME" value="com.gxwebsoft.WebSoftApplication" />
|
||||||
|
<module name="com-gxwebsoft-modules" />
|
||||||
|
<extension name="coverage">
|
||||||
|
<pattern>
|
||||||
|
<option name="PATTERN" value="com.gxwebsoft.*" />
|
||||||
|
<option name="ENABLED" value="true" />
|
||||||
|
</pattern>
|
||||||
|
</extension>
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="JetRunConfigurationType">
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="KotlinStandaloneScriptRunConfigurationType">
|
||||||
|
<option name="filePath" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<recent_temporary>
|
||||||
|
<list>
|
||||||
|
<item itemvalue="应用程序.WebSoftApplication" />
|
||||||
|
</list>
|
||||||
|
</recent_temporary>
|
||||||
|
</component>
|
||||||
|
<component name="SharedIndexes">
|
||||||
|
<attachedChunks>
|
||||||
|
<set>
|
||||||
|
<option value="bundled-jdk-9823dce3aa75-bf35d07a577b-intellij.indexing.shared.core-IU-252.26830.84" />
|
||||||
|
</set>
|
||||||
|
</attachedChunks>
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="默认任务">
|
||||||
|
<changelist id="3ac55b60-f0f7-4a9b-af40-ce3c8e3c4479" name="更改" comment="" />
|
||||||
|
<created>1766987144189</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1766987144189</updated>
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -7,10 +7,12 @@ import cn.binarywang.wx.miniapp.bean.WxMaCodeLineColor;
|
|||||||
import cn.hutool.core.img.ImgUtil;
|
import cn.hutool.core.img.ImgUtil;
|
||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.freewayso.image.combiner.ImageCombiner;
|
import com.freewayso.image.combiner.ImageCombiner;
|
||||||
import com.freewayso.image.combiner.enums.OutputFormat;
|
import com.freewayso.image.combiner.enums.OutputFormat;
|
||||||
|
import com.gxwebsoft.common.core.config.ConfigProperties;
|
||||||
import com.gxwebsoft.common.core.utils.JSONUtil;
|
import com.gxwebsoft.common.core.utils.JSONUtil;
|
||||||
import com.gxwebsoft.common.core.utils.RequestUtil;
|
import com.gxwebsoft.common.core.utils.RequestUtil;
|
||||||
import com.gxwebsoft.common.core.web.BaseController;
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
@@ -26,6 +28,8 @@ import com.gxwebsoft.common.core.annotation.OperationLog;
|
|||||||
import com.gxwebsoft.common.system.entity.User;
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.apache.poi.ss.usermodel.*;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
@@ -33,13 +37,27 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.awt.Color;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 商品记录表控制器
|
* 商品记录表控制器
|
||||||
@@ -51,6 +69,8 @@ import java.util.Map;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/shop/goods")
|
@RequestMapping("/api/shop/goods")
|
||||||
public class GoodsController extends BaseController {
|
public class GoodsController extends BaseController {
|
||||||
|
private static final String DEFAULT_IMPORT_EXCEL_PATH = "/Users/gxwebsoft/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/ip170com_ae28/msg/file/2025-12/产品目录-商城储备12.26(2).xlsx";
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private GoodsService goodsService;
|
private GoodsService goodsService;
|
||||||
@Resource
|
@Resource
|
||||||
@@ -61,6 +81,10 @@ public class GoodsController extends BaseController {
|
|||||||
private MerchantService merchantService;
|
private MerchantService merchantService;
|
||||||
@Resource
|
@Resource
|
||||||
private GoodsRoleCommissionService goodsRoleCommissionService;
|
private GoodsRoleCommissionService goodsRoleCommissionService;
|
||||||
|
@Resource
|
||||||
|
private GoodsCategoryService goodsCategoryService;
|
||||||
|
@Resource
|
||||||
|
private ConfigProperties config;
|
||||||
|
|
||||||
@Value("${config.upload-path}")
|
@Value("${config.upload-path}")
|
||||||
private String uploadPath;
|
private String uploadPath;
|
||||||
@@ -271,6 +295,365 @@ public class GoodsController extends BaseController {
|
|||||||
return success(data);
|
return success(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation("批量导入商品(Excel)")
|
||||||
|
@PostMapping("/import-excel")
|
||||||
|
public ApiResult<GoodsImportResult> importExcel(@RequestParam(value = "path", required = false) String path,
|
||||||
|
@RequestParam(value = "sheetName", required = false) String sheetName,
|
||||||
|
@RequestParam(value = "skipExisting", required = false, defaultValue = "true") Boolean skipExisting,
|
||||||
|
@RequestParam(value = "createCategory", required = false, defaultValue = "true") Boolean createCategory,
|
||||||
|
@RequestParam(value = "merchantId", required = false) Integer merchantId,
|
||||||
|
@RequestParam(value = "defaultIsShow", required = false) Integer defaultIsShow,
|
||||||
|
@RequestParam(value = "defaultStock", required = false) Integer defaultStock) {
|
||||||
|
String excelPath = StrUtil.blankToDefault(path, DEFAULT_IMPORT_EXCEL_PATH);
|
||||||
|
String targetSheetName = StrUtil.blankToDefault(sheetName, "Sheet1");
|
||||||
|
if (!FileUtil.exist(excelPath)) {
|
||||||
|
return fail("Excel文件不存在: " + excelPath,null);
|
||||||
|
}
|
||||||
|
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
Integer importUserId = loginUser == null ? null : loginUser.getUserId();
|
||||||
|
Integer importMerchantId = merchantId != null ? merchantId : (loginUser == null ? 0 : (loginUser.getMerchantId() == null ? 0 : loginUser.getMerchantId()));
|
||||||
|
int importIsShow = defaultIsShow != null ? defaultIsShow : 0;
|
||||||
|
int importStock = defaultStock != null ? defaultStock : 0;
|
||||||
|
|
||||||
|
GoodsImportResult result = new GoodsImportResult();
|
||||||
|
result.setExcelPath(excelPath);
|
||||||
|
result.setSheetName(targetSheetName);
|
||||||
|
|
||||||
|
DataFormatter formatter = new DataFormatter();
|
||||||
|
try (ZipFile zipFile = new ZipFile(excelPath); InputStream in = new FileInputStream(excelPath); Workbook workbook = new XSSFWorkbook(in)) {
|
||||||
|
Sheet sheet = workbook.getSheet(targetSheetName);
|
||||||
|
if (sheet == null) {
|
||||||
|
return fail("未找到工作表: " + targetSheetName,null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> cellImageIdToMediaEntry = buildCellImageIdToMediaEntry(zipFile);
|
||||||
|
Map<String, GoodsCategory> categoryByTitle = loadCategoryMap(importMerchantId);
|
||||||
|
|
||||||
|
Set<String> names = collectGoodsNames(sheet, formatter);
|
||||||
|
Set<String> existingNames = java.util.Collections.emptySet();
|
||||||
|
if (skipExisting && !names.isEmpty()) {
|
||||||
|
existingNames = goodsService.list(new LambdaQueryWrapper<Goods>()
|
||||||
|
.eq(Goods::getMerchantId, importMerchantId)
|
||||||
|
.in(Goods::getGoodsName, names))
|
||||||
|
.stream().map(Goods::getGoodsName).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastRow = sheet.getLastRowNum();
|
||||||
|
for (int i = 1; i <= lastRow; i++) {
|
||||||
|
Row row = sheet.getRow(i);
|
||||||
|
if (row == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String goodsName = getCellString(row, 2, formatter);
|
||||||
|
if (StrUtil.isBlank(goodsName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
goodsName = goodsName.trim();
|
||||||
|
result.setTotalRows(result.getTotalRows() + 1);
|
||||||
|
|
||||||
|
if (skipExisting && existingNames.contains(goodsName)) {
|
||||||
|
result.setSkippedExists(result.getSkippedExists() + 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String actionCategory = getCellString(row, 1, formatter);
|
||||||
|
String brand = getCellString(row, 3, formatter);
|
||||||
|
String manufacturer = getCellString(row, 4, formatter);
|
||||||
|
String spec = getCellString(row, 6, formatter);
|
||||||
|
String categoryName = getCellString(row, 10, formatter);
|
||||||
|
String supplierName = getCellString(row, 11, formatter);
|
||||||
|
|
||||||
|
BigDecimal costPrice = getCellDecimal(row, 8, formatter);
|
||||||
|
BigDecimal salePrice = getCellDecimal(row, 9, formatter);
|
||||||
|
Integer sales = parseSales(getCellString(row, 5, formatter));
|
||||||
|
|
||||||
|
Goods goods = new Goods();
|
||||||
|
goods.setGoodsName(goodsName);
|
||||||
|
goods.setType(0);
|
||||||
|
goods.setSpecs(0);
|
||||||
|
goods.setDeductStockType(10);
|
||||||
|
goods.setStock(importStock);
|
||||||
|
goods.setIsShow(importIsShow);
|
||||||
|
goods.setStatus(1);
|
||||||
|
goods.setMerchantId(importMerchantId);
|
||||||
|
goods.setUserId(importUserId);
|
||||||
|
goods.setUnitName(spec);
|
||||||
|
goods.setSupplierName(supplierName);
|
||||||
|
goods.setSales(sales);
|
||||||
|
goods.setPrice(costPrice);
|
||||||
|
goods.setSalePrice(salePrice != null ? salePrice : costPrice);
|
||||||
|
goods.setOriginPrice(goods.getSalePrice());
|
||||||
|
goods.setCategoryName(categoryName);
|
||||||
|
goods.setComments(buildComments(actionCategory, brand, manufacturer));
|
||||||
|
|
||||||
|
if (StrUtil.isNotBlank(categoryName)) {
|
||||||
|
GoodsCategory category = categoryByTitle.get(categoryName.trim());
|
||||||
|
if (category == null && createCategory) {
|
||||||
|
category = createCategory(importMerchantId, categoryName.trim());
|
||||||
|
categoryByTitle.put(category.getTitle(), category);
|
||||||
|
}
|
||||||
|
if (category != null) {
|
||||||
|
goods.setCategoryId(category.getCategoryId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String imageId = extractDispimgId(row.getCell(7));
|
||||||
|
if (StrUtil.isNotBlank(imageId)) {
|
||||||
|
String mediaEntry = cellImageIdToMediaEntry.get(imageId);
|
||||||
|
if (StrUtil.isNotBlank(mediaEntry)) {
|
||||||
|
String url = saveCellImage(zipFile, mediaEntry, imageId);
|
||||||
|
if (StrUtil.isNotBlank(url)) {
|
||||||
|
goods.setImage(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.getGoods().add(goods);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.getGoods().isEmpty()) {
|
||||||
|
return success("未读取到可导入的数据", result);
|
||||||
|
}
|
||||||
|
if (goodsService.saveBatch(result.getGoods())) {
|
||||||
|
result.setInserted(result.getGoods().size());
|
||||||
|
return success("导入成功", result);
|
||||||
|
}
|
||||||
|
return fail("导入失败", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail("导入异常: " + e.getMessage(), result).setError(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, GoodsCategory> loadCategoryMap(Integer merchantId) {
|
||||||
|
List<GoodsCategory> categories = goodsCategoryService.list(new LambdaQueryWrapper<GoodsCategory>()
|
||||||
|
.eq(GoodsCategory::getMerchantId, merchantId)
|
||||||
|
.eq(GoodsCategory::getDeleted, 0));
|
||||||
|
return categories.stream()
|
||||||
|
.filter(c -> StrUtil.isNotBlank(c.getTitle()))
|
||||||
|
.collect(Collectors.toMap(GoodsCategory::getTitle, c -> c, (a, b) -> a));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> collectGoodsNames(Sheet sheet, DataFormatter formatter) {
|
||||||
|
int lastRow = sheet.getLastRowNum();
|
||||||
|
return java.util.stream.IntStream.rangeClosed(1, lastRow)
|
||||||
|
.mapToObj(sheet::getRow)
|
||||||
|
.filter(r -> r != null)
|
||||||
|
.map(r -> getCellString(r, 2, formatter))
|
||||||
|
.filter(StrUtil::isNotBlank)
|
||||||
|
.map(String::trim)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private GoodsCategory createCategory(Integer merchantId, String title) {
|
||||||
|
GoodsCategory category = new GoodsCategory();
|
||||||
|
category.setTitle(title);
|
||||||
|
category.setType(0);
|
||||||
|
category.setParentId(0);
|
||||||
|
category.setMerchantId(merchantId);
|
||||||
|
category.setStatus(0);
|
||||||
|
category.setSortNumber(0);
|
||||||
|
goodsCategoryService.save(category);
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildComments(String actionCategory, String brand, String manufacturer) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (StrUtil.isNotBlank(actionCategory)) sb.append("作用分类:").append(actionCategory.trim()).append(" ");
|
||||||
|
if (StrUtil.isNotBlank(brand)) sb.append("品牌:").append(brand.trim()).append(" ");
|
||||||
|
if (StrUtil.isNotBlank(manufacturer)) sb.append("厂家:").append(manufacturer.trim()).append(" ");
|
||||||
|
return sb.toString().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getCellString(Row row, int cellIndex, DataFormatter formatter) {
|
||||||
|
Cell cell = row.getCell(cellIndex, Row.MissingCellPolicy.RETURN_BLANK_AS_NULL);
|
||||||
|
if (cell == null) return null;
|
||||||
|
String v = formatter.formatCellValue(cell);
|
||||||
|
return StrUtil.trimToNull(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BigDecimal getCellDecimal(Row row, int cellIndex, DataFormatter formatter) {
|
||||||
|
String v = getCellString(row, cellIndex, formatter);
|
||||||
|
if (StrUtil.isBlank(v)) return null;
|
||||||
|
v = v.replace(",", "").replace("元", "").trim();
|
||||||
|
if (v.endsWith("+")) v = v.substring(0, v.length() - 1);
|
||||||
|
try {
|
||||||
|
return new BigDecimal(v);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Integer parseSales(String v) {
|
||||||
|
if (StrUtil.isBlank(v)) return null;
|
||||||
|
Matcher m = Pattern.compile("(\\d+)").matcher(v);
|
||||||
|
if (m.find()) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(m.group(1));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractDispimgId(Cell cell) {
|
||||||
|
if (cell == null) return null;
|
||||||
|
String formula = null;
|
||||||
|
try {
|
||||||
|
if (cell.getCellType() == CellType.FORMULA) {
|
||||||
|
formula = cell.getCellFormula();
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
String v = StrUtil.blankToDefault(formula, cell.toString());
|
||||||
|
Matcher matcher = Pattern.compile("ID_[0-9A-Fa-f]+").matcher(v);
|
||||||
|
return matcher.find() ? matcher.group() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String saveCellImage(ZipFile zipFile, String mediaEntryName, String imageId) {
|
||||||
|
ZipEntry entry = zipFile.getEntry(mediaEntryName);
|
||||||
|
if (entry == null) return null;
|
||||||
|
String ext = ".jpg";
|
||||||
|
int dot = mediaEntryName.lastIndexOf('.');
|
||||||
|
if (dot > 0 && dot < mediaEntryName.length() - 1) {
|
||||||
|
ext = "." + mediaEntryName.substring(dot + 1);
|
||||||
|
}
|
||||||
|
String relativePath = "goodsImport/" + imageId + ext;
|
||||||
|
File out = FileUtil.file(uploadPath, relativePath);
|
||||||
|
try {
|
||||||
|
if (!out.exists()) {
|
||||||
|
FileUtil.mkParentDirs(out);
|
||||||
|
try (InputStream in = zipFile.getInputStream(entry)) {
|
||||||
|
FileUtil.writeFromStream(in, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String fileServer = config.getFileServer();
|
||||||
|
if (StrUtil.isBlank(fileServer)) {
|
||||||
|
return serverUrl.concat("/").concat(relativePath);
|
||||||
|
}
|
||||||
|
return StrUtil.removeSuffix(fileServer, "/") + "/api/file/" + relativePath;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, String> buildCellImageIdToMediaEntry(ZipFile zipFile) {
|
||||||
|
final String PKG_REL_NS = "http://schemas.openxmlformats.org/package/2006/relationships";
|
||||||
|
final String XDR_NS = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing";
|
||||||
|
final String A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
||||||
|
final String R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
||||||
|
|
||||||
|
Map<String, String> rIdToTarget = new HashMap<>();
|
||||||
|
Map<String, String> idToMedia = new HashMap<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
ZipEntry relsEntry = zipFile.getEntry("xl/_rels/cellimages.xml.rels");
|
||||||
|
ZipEntry cellImagesEntry = zipFile.getEntry("xl/cellimages.xml");
|
||||||
|
if (relsEntry == null || cellImagesEntry == null) {
|
||||||
|
return idToMedia;
|
||||||
|
}
|
||||||
|
|
||||||
|
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||||
|
dbf.setNamespaceAware(true);
|
||||||
|
|
||||||
|
Document relsDoc;
|
||||||
|
try (InputStream relsIn = zipFile.getInputStream(relsEntry)) {
|
||||||
|
relsDoc = dbf.newDocumentBuilder().parse(relsIn);
|
||||||
|
}
|
||||||
|
NodeList relNodes = relsDoc.getElementsByTagNameNS(PKG_REL_NS, "Relationship");
|
||||||
|
for (int i = 0; i < relNodes.getLength(); i++) {
|
||||||
|
Element el = (Element) relNodes.item(i);
|
||||||
|
String id = el.getAttribute("Id");
|
||||||
|
String target = el.getAttribute("Target");
|
||||||
|
if (StrUtil.isNotBlank(id) && StrUtil.isNotBlank(target)) {
|
||||||
|
String normalizedTarget = target.startsWith("/") ? target.substring(1) : target;
|
||||||
|
rIdToTarget.put(id, "xl/" + normalizedTarget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Document imagesDoc;
|
||||||
|
try (InputStream imagesIn = zipFile.getInputStream(cellImagesEntry)) {
|
||||||
|
imagesDoc = dbf.newDocumentBuilder().parse(imagesIn);
|
||||||
|
}
|
||||||
|
NodeList pics = imagesDoc.getElementsByTagNameNS(XDR_NS, "pic");
|
||||||
|
for (int i = 0; i < pics.getLength(); i++) {
|
||||||
|
Element pic = (Element) pics.item(i);
|
||||||
|
NodeList cNvPrNodes = pic.getElementsByTagNameNS(XDR_NS, "cNvPr");
|
||||||
|
NodeList blipNodes = pic.getElementsByTagNameNS(A_NS, "blip");
|
||||||
|
if (cNvPrNodes.getLength() == 0 || blipNodes.getLength() == 0) continue;
|
||||||
|
|
||||||
|
Element cNvPr = (Element) cNvPrNodes.item(0);
|
||||||
|
Element blip = (Element) blipNodes.item(0);
|
||||||
|
String name = cNvPr.getAttribute("name");
|
||||||
|
String embed = blip.getAttributeNS(R_NS, "embed");
|
||||||
|
String target = rIdToTarget.get(embed);
|
||||||
|
if (StrUtil.isNotBlank(name) && StrUtil.isNotBlank(target)) {
|
||||||
|
idToMedia.put(name, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return idToMedia;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GoodsImportResult {
|
||||||
|
private String excelPath;
|
||||||
|
private String sheetName;
|
||||||
|
private int totalRows;
|
||||||
|
private int inserted;
|
||||||
|
private int skippedExists;
|
||||||
|
private List<Goods> goods = new java.util.ArrayList<>();
|
||||||
|
|
||||||
|
public String getExcelPath() {
|
||||||
|
return excelPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExcelPath(String excelPath) {
|
||||||
|
this.excelPath = excelPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSheetName() {
|
||||||
|
return sheetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSheetName(String sheetName) {
|
||||||
|
this.sheetName = sheetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalRows() {
|
||||||
|
return totalRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotalRows(int totalRows) {
|
||||||
|
this.totalRows = totalRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInserted() {
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInserted(int inserted) {
|
||||||
|
this.inserted = inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSkippedExists() {
|
||||||
|
return skippedExists;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSkippedExists(int skippedExists) {
|
||||||
|
this.skippedExists = skippedExists;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Goods> getGoods() {
|
||||||
|
return goods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGoods(List<Goods> goods) {
|
||||||
|
this.goods = goods;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ApiOperation("生成海报")
|
@ApiOperation("生成海报")
|
||||||
@PostMapping("/make-goods-poster/{goodsId}")
|
@PostMapping("/make-goods-poster/{goodsId}")
|
||||||
public ApiResult<?> makePoster(@PathVariable Integer goodsId) throws Exception {
|
public ApiResult<?> makePoster(@PathVariable Integer goodsId) throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user