package com.gxwebsoft.shop.controller;

import cn.binarywang.wx.miniapp.api.WxMaQrcodeService;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaQrcodeServiceImpl;
import cn.binarywang.wx.miniapp.bean.WxMaCodeLineColor;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
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.update.LambdaUpdateWrapper;
import com.freewayso.image.combiner.ImageCombiner;
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.RequestUtil;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.system.entity.DictData;
import com.gxwebsoft.shop.entity.*;
import com.gxwebsoft.shop.service.*;
import com.gxwebsoft.shop.param.GoodsParam;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.annotation.OperationLog;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.shop.vo.GoodsExcelImportResult;
import io.swagger.annotations.Api;
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.Value;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.xml.parsers.DocumentBuilderFactory;
import java.awt.*;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
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;

/**
 * 商品记录表控制器
 *
 * @author 科技小王子
 * @since 2024-07-25 19:22:01
 */
@Api(tags = "商品记录表管理")
@RestController
@RequestMapping("/api/shop/goods")
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
    private GoodsService goodsService;
    @Resource
    private GoodsSpecService goodsSpecService;
    @Resource
    private GoodsSkuService goodsSkuService;
    @Resource
    private MerchantService merchantService;
    @Resource
    private GoodsRoleCommissionService goodsRoleCommissionService;
    @Resource
    private GoodsCategoryService goodsCategoryService;
    @Resource
    private ConfigProperties config;

    @Value("${config.upload-path}")
    private String uploadPath;
    final private String serverUrl = "https://nbg-img.websoft.top";

    @Autowired
    private RequestUtil requestUtil;
    @Autowired
    private WeChatController weChatController;

    @ApiOperation("分页查询商品记录表")
    @GetMapping("/page")
    public ApiResult<PageResult<Goods>> page(GoodsParam param) {
        final User loginUser = getLoginUser();
        param.setLoginUser(loginUser);
        return success(goodsService.pageRel(param));
    }

    @ApiOperation("查询全部商品记录表")
    @GetMapping()
    public ApiResult<List<Goods>> list(GoodsParam param) {
        // 使用关联查询
        return success(goodsService.listRel(param));
    }

    @ApiOperation("根据id查询商品记录表")
    @GetMapping("/{id}")
    public ApiResult<Goods> get(@PathVariable("id") Integer id) {
        // 使用关联查询
        final Goods goods = goodsService.getByIdRel(id);
        if (getLoginUser() != null) {
            User parentUser = requestUtil.getParent(getLoginUserId());
            BigDecimal originPrice = goods.getPrice();
            goods.setPrice(goods.getSalePrice());
            // 会员
//            if (parentUser != null) {
            if (goods.getPriceGift()) goods.setShowGift(true);
//                goods.setPrice(originPrice);
//            }
            // 会员店
//            if (getLoginUser().getGradeId() > 30 && goods.getBuyingPriceOpen().equals(1)) {
//                goods.setPrice(goods.getBuyingPrice());
////                if (goods.getPriceGift()) goods.setShowGift(true);
//            }

        }

        // 提取specValue
        final GoodsSpec spec = goodsSpecService.getOne(new LambdaQueryWrapper<GoodsSpec>().eq(GoodsSpec::getGoodsId, id).last("limit 1"));
        if (spec != null) {
            final String specValue = spec.getSpecValue();
            final Object object = JSONUtil.parseObject(specValue, Object.class);
            goods.setGoodsSpecValue(object);
            goods.setGoodsSpec(spec);
        }
        goods.setGoodsSkus(goodsSkuService.listByGoods(id));
        // 整理商品价格
        final User loginUser = getLoginUser();
        if (loginUser != null) {
            User parentUser = requestUtil.getParent(getLoginUserId());
            BigDecimal originPrice = goods.getPrice();
            goods.setPrice(goods.getSalePrice());
//            if (loginUser.getGradeId().equals(33)) {
//                goods.setPrice(goods.getDealerPrice());
//            }
//            if (loginUser.getGradeId().equals(0)) {
//                goods.setPrice(originPrice);
//                goods.setPrice(goods.getSalePrice());
//            }
            // 会员
//            if (parentUser != null) {
            if (goods.getPriceGift()) goods.setShowGift(true);
//                goods.setPrice(originPrice);
//            }
            // 会员店
//            if (getLoginUser().getGradeId() > 30 && goods.getBuyingPriceOpen().equals(1)) {
//                goods.setPrice(goods.getBuyingPrice());
////                if (goods.getPriceGift()) goods.setShowGift(true);
//            }
        } else {
            goods.setPrice(goods.getSalePrice());
        }
        // 店铺信息
        if (goods.getMerchantId() != null) {
            Merchant merchant = merchantService.getById(goods.getMerchantId());
            goods.setMerchant(merchant);
            if (merchant != null) {
                goods.setMerchantName(merchant.getMerchantName());
            }
        }

        return success(goods);
    }

    @ApiOperation("添加商品记录表")
    @PostMapping()
    public ApiResult<?> save(@RequestBody Goods goods) {
        // 记录当前登录用户id
        User loginUser = getLoginUser();
        if (loginUser != null) {
            goods.setUserId(loginUser.getUserId());
            goods.setMerchantId(loginUser.getMerchantId());
        }
        if (goodsService.save(goods)) {
            goodsRoleCommissionService.removeByMap(new HashMap<>() {{
                put("goods_id", goods.getGoodsId());
            }});
            if (goods.getGoodsRoleCommission() != null && !goods.getGoodsRoleCommission().isEmpty()) {
                for (GoodsRoleCommission goodsRoleCommission : goods.getGoodsRoleCommission()) {
                    goodsRoleCommission.setGoodsId(goods.getGoodsId());
                }
                goodsRoleCommissionService.saveBatch(goods.getGoodsRoleCommission());
            }
            // 保存商品规格及sku信息
            goodsService.saveBatchByAsync(goods);
            return success("添加成功");
        }
        return fail("添加失败");
    }

    @ApiOperation("修改商品记录表")
    @PutMapping()
    public ApiResult<?> update(@RequestBody Goods goods) {
        if (goodsService.updateById(goods)) {
            goodsRoleCommissionService.removeByMap(new HashMap<>() {{
                put("goods_id", goods.getGoodsId());
            }});
            if (goods.getGoodsRoleCommission() != null && !goods.getGoodsRoleCommission().isEmpty()) {
                for (GoodsRoleCommission goodsRoleCommission : goods.getGoodsRoleCommission()) {
                    goodsRoleCommission.setGoodsId(goods.getGoodsId());
                }
                goodsRoleCommissionService.saveBatch(goods.getGoodsRoleCommission());
            }
            // 删除旧信息
            goodsSpecService.remove(new LambdaQueryWrapper<GoodsSpec>().eq(GoodsSpec::getGoodsId, goods.getGoodsId()));
            goodsSkuService.remove(new LambdaQueryWrapper<GoodsSku>().eq(GoodsSku::getGoodsId, goods.getGoodsId()));
            // 保存商品规格及sku信息
            goodsService.saveBatchByAsync(goods);
            return success("修改成功");
        }
        return fail("修改失败");
    }

    @ApiOperation("删除商品记录表")
    @DeleteMapping("/{id}")
    public ApiResult<?> remove(@PathVariable("id") Integer id) {
        if (goodsService.removeById(id)) {
            return success("删除成功");
        }
        return fail("删除失败");
    }

    @ApiOperation("批量添加商品记录表")
    @PostMapping("/batch")
    public ApiResult<?> saveBatch(@RequestBody List<Goods> list) {
        if (goodsService.saveBatch(list)) {
            return success("添加成功");
        }
        return fail("添加失败");
    }

    @ApiOperation("批量修改商品记录表")
    @PutMapping("/batch")
    public ApiResult<?> removeBatch(@RequestBody BatchParam<Goods> batchParam) {
        if (batchParam.update(goodsService, "goods_id")) {
            return success("修改成功");
        }
        return fail("修改失败");
    }

    @ApiOperation("批量删除商品记录表")
    @DeleteMapping("/batch")
    public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
        if (goodsService.removeByIds(ids)) {
            return success("删除成功");
        }
        return fail("删除失败");
    }

    @ApiOperation("统计信息")
    @GetMapping("/data")
    public ApiResult<Map<String, Integer>> data(GoodsParam param) {
        Map<String, Integer> data = new HashMap<>();
        final LambdaQueryWrapper<Goods> wrapper = new LambdaQueryWrapper<>();

        if (param.getMerchantId() != null) {
            wrapper.eq(Goods::getMerchantId, param.getMerchantId());
        }

        Integer totalNum = goodsService.count(
                wrapper.eq(Goods::getIsShow, 1).gt(Goods::getStock, 0)
        );
        data.put("totalNum", totalNum);

        Integer totalNum2 = goodsService.count(
                wrapper.eq(Goods::getIsShow, 0)
        );
        data.put("totalNum2", totalNum2);

        Integer totalNum3 = goodsService.count(
                wrapper.eq(Goods::getStock, 0)
        );
        data.put("totalNum3", totalNum3);

        // 下架已售罄的商品
        goodsService.update(new LambdaUpdateWrapper<Goods>().eq(Goods::getStock, 0).eq(Goods::getIsShow, 1).set(Goods::getIsShow, 0));

        return success(data);
    }

    @ApiOperation("批量导入商品(Excel)")
    @PostMapping("/import-excel")
    public ApiResult<GoodsExcelImportResult> 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");
        GoodsExcelImportResult result = new GoodsExcelImportResult();
        result.setExcelPath(excelPath);
        result.setSheetName(targetSheetName);
        ResolvedExcelFile resolvedExcelFile;
        try {
            resolvedExcelFile = resolveExcelFile(excelPath);
        } catch (IllegalArgumentException e) {
            return fail(e.getMessage(), result);
        } catch (Exception e) {
            return fail("Excel文件读取失败: " + e.getMessage(), result).setError(e.toString());
        }

        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;
        result.setMerchantId(importMerchantId);

        DataFormatter formatter = new DataFormatter();
        List<Goods> toInsert = new java.util.ArrayList<>();
        try (ZipFile zipFile = new ZipFile(resolvedExcelFile.file);
             InputStream in = new FileInputStream(resolvedExcelFile.file);
             Workbook workbook = new XSSFWorkbook(in)) {
            Sheet sheet = workbook.getSheet(targetSheetName);
            if (sheet == null) {
                return fail("未找到工作表: " + targetSheetName, result);
            }

            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);
                        result.setCreatedCategories(result.getCreatedCategories() + 1);
                    }
                    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.setSavedImages(result.getSavedImages() + 1);
                        }
                    }
                }

                toInsert.add(goods);
            }

            if (toInsert.isEmpty()) {
                return success("未读取到可导入的数据", result);
            }
            if (goodsService.saveBatch(toInsert)) {
                result.setInserted(toInsert.size());
                return success("导入成功", result);
            }
            return fail("导入失败", result);
        } catch (Exception e) {
            return fail("导入异常: " + e.getMessage(), result).setError(e.toString());
        } finally {
            resolvedExcelFile.cleanupQuietly();
        }
    }

    private static final class ResolvedExcelFile {
        private final File file;
        private final boolean deleteOnClose;

        private ResolvedExcelFile(File file, boolean deleteOnClose) {
            this.file = file;
            this.deleteOnClose = deleteOnClose;
        }

        private void cleanupQuietly() {
            if (!deleteOnClose) {
                return;
            }
            try {
                FileUtil.del(file);
            } catch (Exception ignored) {
            }
        }
    }

    private ResolvedExcelFile resolveExcelFile(String excelPath) throws Exception {
        if (StrUtil.isBlank(excelPath)) {
            throw new IllegalArgumentException("Excel路径为空");
        }
        String trimmed = excelPath.trim();
        if (StrUtil.startWithIgnoreCase(trimmed, "http://") || StrUtil.startWithIgnoreCase(trimmed, "https://")) {
            String suffix = ".xlsx";
            String withoutQuery = StrUtil.subBefore(trimmed, "?", false);
            int dot = withoutQuery.lastIndexOf('.');
            if (dot > 0 && dot < withoutQuery.length() - 1) {
                suffix = "." + withoutQuery.substring(dot + 1);
            }
            File tmp = File.createTempFile("goods-import-", suffix);
            HttpUtil.downloadFile(trimmed, tmp);
            if (!tmp.exists() || tmp.length() == 0) {
                FileUtil.del(tmp);
                throw new IllegalStateException("Excel下载失败或文件为空");
            }
            return new ResolvedExcelFile(tmp, true);
        }
        if (StrUtil.startWithIgnoreCase(trimmed, "file:")) {
            File f = new File(URI.create(trimmed));
            if (!f.exists()) {
                throw new IllegalArgumentException("Excel文件不存在: " + excelPath);
            }
            return new ResolvedExcelFile(f, false);
        }
        if (!FileUtil.exist(trimmed)) {
            throw new IllegalArgumentException("Excel文件不存在: " + excelPath);
        }
        return new ResolvedExcelFile(FileUtil.file(trimmed), false);
    }

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

    @ApiOperation("生成海报")
    @PostMapping("/make-goods-poster/{goodsId}")
    public ApiResult<?> makePoster(@PathVariable Integer goodsId) throws Exception {
        Goods goods = goodsService.getById(goodsId);
        User loginUser = getLoginUser();
        String filePath = "qr/goodsPoster_" + loginUser.getUserId() + "_" + goodsId + ".png";
        String savePath = uploadPath + filePath;

        String qr = uploadPath + "qr/userGoodsQr_" + loginUser.getUserId() + "_" + goodsId + ".png";
        if (!FileUtil.exist(qr)) {
            weChatController.makeQr(qr, loginUser.getUserId() + "_" + goodsId, "package/goods/detail");
        }

        String goodsCover = uploadPath + "goodsCover/" + goodsId + ".png";
        if (!FileUtil.exist(goodsCover)) {
            if (!goods.getImage().contains("png")) {
                // 获取后缀
                String suffix = goods.getImage().substring(goods.getImage().lastIndexOf(".") + 1);
                goodsCover = goodsCover.replace("png", suffix);
            }
            String downloadUrl = goods.getImage();
            if (downloadUrl.contains("mp4")) {
                downloadUrl = downloadUrl + "?x-oss-process=video/snapshot,t_2000,f_jpg,w_1280";
            }else if (!downloadUrl.contains("png")) {
                downloadUrl = downloadUrl + "?x-oss-process=image/format,png";
            }
            HttpUtil.downloadFile(downloadUrl, FileUtil.file(goodsCover));

        }

        File qrFile = new File(qr);
        String qrPath = "file://" + qrFile.getAbsolutePath();

        File goodsCoverFile = new File(goodsCover);
        String goodsCoverPath = "file://" + goodsCoverFile.getAbsolutePath();

        ImageCombiner postCombiner = new ImageCombiner(1024, 1280, Color.white, OutputFormat.PNG);

        // 封面图
        Image goodsCoverImage = ImgUtil.read(goodsCoverPath);
        goodsCoverImage = ImgUtil.scale(goodsCoverImage, 1024, 1024);
        postCombiner.addImageElement(ImgUtil.toBufferedImage(goodsCoverImage), 0, 0);

        // 二维码
        Image qrImg = ImgUtil.read(qrPath);
        qrImg = ImgUtil.scale(qrImg, 180, 180);
        postCombiner.addImageElement(ImgUtil.toBufferedImage(qrImg), 820, 1052);

        // 价格
        postCombiner.addTextElement("￥" + goods.getSalePrice(), 60, 24, 1052).setColor(255, 0, 0);

        // 标题
        String goodsOriginName = goods.getGoodsName();
        String goodsName = goods.getGoodsName().length() > 20 ? goods.getGoodsName().substring(0, 20) : goods.getGoodsName();

        postCombiner.addTextElement(goodsName, 30, 24, 1180).setColor(200, 200, 200);

        if (goodsOriginName.length() > 20) {
            String goodsName2 = goodsOriginName.substring(20);
            goodsName2 += "···";
            postCombiner.addTextElement(goodsName2, 30, 24, 1230).setColor(200, 200, 200);
        }

        postCombiner.addTextElement("长按识别查看", 26, 820, 1230).setColor(200, 200, 200);

        //执行图片合并
        postCombiner.combine();
        //可以保存到本地
        postCombiner.save(savePath);
        return success("生成成功", serverUrl.concat("/" + filePath));
    }

    @ApiOperation("生成海报")
    @PostMapping("/make-shop-goods-poster/{merchantId}/{goodsId}")
    public ApiResult<?> makeShopPoster(@PathVariable Integer merchantId, @PathVariable Integer goodsId) throws Exception {
        Goods goods = goodsService.getById(goodsId);
        User loginUser = getLoginUser();
        String filePath = "qr/goodsPoster_" + loginUser.getUserId() + "_" + merchantId + "_" + goodsId + ".png";
        String savePath = uploadPath + filePath;

        String qr = uploadPath + "qr/userGoodsQr_" + loginUser.getUserId() + "_" + merchantId + "_" + goodsId + ".png";
        if (!FileUtil.exist(qr)) {
            weChatController.makeQr(qr, "share_" + loginUser.getUserId() + "_" + merchantId + "_" + goodsId, "package/shop/detail");
        }

        String goodsCover = uploadPath + "goodsCover/" + goodsId + ".png";
        if (!FileUtil.exist(goodsCover)) {
            if (!goods.getImage().contains("png")) {
                // 获取后缀
                String suffix = goods.getImage().substring(goods.getImage().lastIndexOf(".") + 1);
                goodsCover = goodsCover.replace("png", suffix);
            }
            String downloadUrl = goods.getImage();
            if (downloadUrl.contains("mp4")) {
                downloadUrl = downloadUrl + "?x-oss-process=video/snapshot,t_2000,f_jpg,w_1280";
            }else if (!downloadUrl.contains("png")) {
                downloadUrl = downloadUrl + "?x-oss-process=image/format,png";
            }
            HttpUtil.downloadFile(downloadUrl, FileUtil.file(goodsCover));

        }

        File qrFile = new File(qr);
        String qrPath = "file://" + qrFile.getAbsolutePath();

        File goodsCoverFile = new File(goodsCover);
        String goodsCoverPath = "file://" + goodsCoverFile.getAbsolutePath();

        ImageCombiner postCombiner = new ImageCombiner(1024, 1480, Color.white, OutputFormat.PNG);

        postCombiner.addTextElement("牛八哥会员店", 30, 24, 40).setColor(200, 200, 200).setCenter(true);
        postCombiner.addTextElement(loginUser.getUsername(), 30, 24, 100).setColor(200, 200, 200).setCenter(true);

        // 封面图
        Image goodsCoverImage = ImgUtil.read(goodsCoverPath);
        goodsCoverImage = ImgUtil.scale(goodsCoverImage, 1024, 1024);
        postCombiner.addImageElement(ImgUtil.toBufferedImage(goodsCoverImage), 0, 200);

        // 二维码
        Image qrImg = ImgUtil.read(qrPath);
        qrImg = ImgUtil.scale(qrImg, 180, 180);
        postCombiner.addImageElement(ImgUtil.toBufferedImage(qrImg), 820, 1252);

        // 价格
        postCombiner.addTextElement("￥" + goods.getPrice(), 60, 24, 1252).setColor(255, 0, 0);

        // 标题
        String goodsOriginName = goods.getGoodsName();
        String goodsName = goods.getGoodsName().length() > 20 ? goods.getGoodsName().substring(0, 20) : goods.getGoodsName();

        postCombiner.addTextElement(goodsName, 30, 24, 1380).setColor(200, 200, 200);

        if (goodsOriginName.length() > 20) {
            String goodsName2 = goodsOriginName.substring(20);
            goodsName2 += "···";
            postCombiner.addTextElement(goodsName2, 30, 24, 1430).setColor(200, 200, 200);
        }

        postCombiner.addTextElement("长按识别查看", 26, 820, 1430).setColor(200, 200, 200);

        //执行图片合并
        postCombiner.combine();
        //可以保存到本地
        postCombiner.save(savePath);
        return success("生成成功", serverUrl.concat("/" + filePath));
    }

}
