package com.slodon.b2b2c.controller.goods.seller;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSON;
import com.slodon.b2b2c.core.config.DomainUrlUtil;
import com.slodon.b2b2c.core.constant.GoodsCategoryConst;
import com.slodon.b2b2c.core.constant.StoreConst;
import com.slodon.b2b2c.core.controller.BaseController;
import com.slodon.b2b2c.core.exception.MallException;
import com.slodon.b2b2c.core.response.JsonResult;
import com.slodon.b2b2c.core.response.SldResponse;
import com.slodon.b2b2c.core.util.AssertUtil;
import com.slodon.b2b2c.core.util.FileDownloadUtils;
import com.slodon.b2b2c.core.util.StringUtil;
import com.slodon.b2b2c.core.util.UserUtil;
import com.slodon.b2b2c.enums.GoodsImportEnum;
import com.slodon.b2b2c.goods.dto.GoodsPublishFrontParamDTO;
import com.slodon.b2b2c.goods.dto.GoodsPublishInsertDTO;
import com.slodon.b2b2c.goods.example.GoodsCategoryExample;
import com.slodon.b2b2c.goods.example.GoodsFreightTemplateExample;
import com.slodon.b2b2c.goods.pojo.GoodsCategory;
import com.slodon.b2b2c.goods.pojo.GoodsFreightTemplate;
import com.slodon.b2b2c.model.goods.GoodsCategoryModel;
import com.slodon.b2b2c.model.goods.GoodsFreightTemplateModel;
import com.slodon.b2b2c.model.goods.GoodsSellerModel;
import com.slodon.b2b2c.model.seller.StoreBindCategoryModel;
import com.slodon.b2b2c.model.seller.StoreModel;
import com.slodon.b2b2c.seller.example.StoreBindCategoryExample;
import com.slodon.b2b2c.seller.pojo.Store;
import com.slodon.b2b2c.seller.pojo.StoreBindCategory;
import com.slodon.b2b2c.seller.pojo.Vendor;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;

import static com.slodon.b2b2c.enums.GoodsImportEnum.*;
import static org.apache.poi.ss.usermodel.Cell.*;

/**
 * 商户后台商品管理
 *
 * @author slodon
 */
@Api(tags = "seller-商品导入")
@RestController
@Slf4j
@RequestMapping("v3/goods/seller/goods")
public class GoodsSellerImportController extends BaseController {

    private static final Integer MAX_IMPORT_GOODS = 6000;
    @Resource
    private GoodsSellerModel goodsSellerModel;
    @Resource
    private StoreModel storeModel;
    @Resource
    private GoodsCategoryModel goodsCategoryModel;
    @Resource
    private StoreBindCategoryModel storeBindCategoryModel;
    @Resource
    private GoodsFreightTemplateModel goodsFreightTemplateModel;

    @ApiOperation("商品导入模板下载")
    @GetMapping("download")
    public JsonResult<Object> templateDownload(HttpServletRequest request, HttpServletResponse response) throws IOException {
        Vendor vendor = UserUtil.getUser(request, Vendor.class);
        Store store = storeModel.getStoreByStoreId(vendor.getStoreId());

        //生成模板
        HSSFWorkbook workbook = new HSSFWorkbook();
        Sheet sheet = workbook.createSheet("录入模板");
        Drawing drawingPatriarch = sheet.createDrawingPatriarch();//用于添加备注
        DataValidationHelper helper = sheet.getDataValidationHelper();//用于添加下拉菜单（数据有效性）
        //下拉菜单-分类
        sheet.addValidationData(this.getGoodsCategoryValidation(helper, store, workbook));
        //下拉菜单-运费模板
        DataValidation freightValidation = this.getFreightValidation(helper, store, workbook);
        if (freightValidation != null) {
            sheet.addValidationData(freightValidation);
        }
        //下拉菜单-是否开增票
        sheet.addValidationData(helper.createValidation(DVConstraint.createExplicitListConstraint("是,否".split(",")),
                new CellRangeAddressList(1, MAX_IMPORT_GOODS + 1, 是否开增票.getColNo(), 是否开增票.getColNo())));
        //下拉菜单-发布状态
        sheet.addValidationData(helper.createValidation(DVConstraint.createExplicitListConstraint("立即售卖,暂不售卖，放入仓库中".split(",")),
                new CellRangeAddressList(1, MAX_IMPORT_GOODS + 1, 发布状态.getColNo(), 发布状态.getColNo())));
        //下拉菜单-商品推荐
        sheet.addValidationData(helper.createValidation(DVConstraint.createExplicitListConstraint("是,否".split(",")),
                new CellRangeAddressList(1, MAX_IMPORT_GOODS + 1, 商品推荐.getColNo(), 商品推荐.getColNo())));

        //写表头
        Row titleRow = sheet.createRow(0);
        for (GoodsImportEnum value : GoodsImportEnum.values()) {
            int titleCol = value.getColNo();
            Cell cell = titleRow.createCell(titleCol);
            sheet.setColumnWidth(titleCol, 256 * value.getColumnWidth() + 184);
            cell.setCellStyle(getTitleStyle(workbook));
            if (value.getRequired()) {
                //必填，在内容前增加一个红色的*
                Font font1 = workbook.createFont();
                font1.setColor(Font.COLOR_RED);
                font1.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);

                HSSFRichTextString richTextString = new HSSFRichTextString("*" + value.name());
                Font font2 = workbook.createFont();
                font2.setColor(Font.COLOR_NORMAL);
                font2.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);

                richTextString.applyFont(0, 1, font1);
                richTextString.applyFont(1, richTextString.length(), font2);
                cell.setCellValue(richTextString);
            } else {
                cell.setCellValue(value.name());
            }

            //增加批注
            if (!StringUtils.isEmpty(value.getDes())) {
                Comment cellComment = drawingPatriarch.createCellComment(new HSSFClientAnchor());
                cellComment.setString(new HSSFRichTextString(value.getDes()));
                cell.setCellComment(cellComment);
            }
        }

        FileDownloadUtils.setExcelHeadInfo(response, request, "商品导入模板.xls");
        workbook.write(response.getOutputStream());
        return null;
    }


    @ApiOperation("商品导入，state==267：文件检测失败，data=>批注文件地址")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "file", value = "文件路径", required = true, paramType = "formData")
    })
    @PostMapping("import")
    public JsonResult<Object> goodsImport(HttpServletRequest request, MultipartFile file, HttpServletResponse response) throws IOException {
        Vendor vendor = UserUtil.getUser(request, Vendor.class);
        Store store = storeModel.getStoreByStoreId(vendor.getStoreId());
        //存储错误批注的文件存储路径
        List<String> errFileUrlList = new ArrayList<>();
        //Map<Integer/*rowNo.*/, List<String>/*图片列表，相对地址*/> 商品图片地址，相对地址
        Map<Integer, List<String>> goodsImageMap = new HashMap<>(16);

        //Map<Integer/*rowNo.*/, String/*视频相对地址*/> 商品视频地址，相对地址
        Map<Integer, String> goodsVideoMap = new HashMap<>();
        Boolean checkResult = this.checkFile(file, store, errFileUrlList, goodsImageMap, goodsVideoMap);
        if (!checkResult) {
            //校验不通过，将文件上传保存，并通知前端
            JsonResult<Object> result = SldResponse.failSpecial("导入文件内容有误");
            result.setData(errFileUrlList.get(0));
            return result;
        }
        //校验通过，新增商品
        this.importGoods(file, vendor, goodsImageMap, goodsVideoMap);
        return SldResponse.success("商品导入成功");
    }

    /**
     * 导入商品入库
     *
     * @param file
     * @param vendor
     * @throws IOException 异常
     */
    private void importGoods(MultipartFile file, Vendor vendor,
                             Map<Integer/*rowNo.*/, List<String>/*图片列表，相对地址*/> goodsImageMap,
                             Map<Integer/*rowNo.*/, String/*视频相对地址*/> goodsVideoMap) throws IOException {
        Workbook workbook = new HSSFWorkbook(file.getInputStream());
        Sheet sheet = workbook.getSheetAt(0);
        int importGoodsNum = sheet.getPhysicalNumberOfRows();
        for (int i = 1; i < importGoodsNum; i++) {//第0行为标题,不插入
            Row row = sheet.getRow(i);
            GoodsPublishFrontParamDTO dto = new GoodsPublishFrontParamDTO();
            dto.setGoodsName(this.getCellValue(row.getCell(商品名称.getColNo()), ""));
            dto.setGoodsBrief(this.getCellValue(row.getCell(广告语.getColNo()), null));
            dto.setCategoryId3(Integer.valueOf(this.getCellValue(row.getCell(商品分类.getColNo()), "").split(">")[0]));
            dto.setFreightId(this.getCellValue(row.getCell(运费模板.getColNo()), null) != null
                    ? Integer.valueOf(this.getCellValue(row.getCell(运费模板.getColNo()), null).split(">")[0]) : null);
            //存在运费模板就不处理固定运费
            if (StringUtil.isNullOrZero(dto.getFreightId())) {
                dto.setFreightFee(this.getCellValue(row.getCell(固定运费.getColNo()), null) != null ?
                        new BigDecimal(this.getCellValue(row.getCell(固定运费.getColNo()), null)).setScale(2, RoundingMode.HALF_UP) : null);
            }
            dto.setIsVatInvoice("是".equals(this.getCellValue(row.getCell(是否开增票.getColNo()), "否")) ? 1 : 0);
            dto.setSellNow("立即售卖".equals(this.getCellValue(row.getCell(发布状态.getColNo()), "暂不售卖，放入仓库中")));
            dto.setStoreIsRecommend("是".equals(this.getCellValue(row.getCell(商品推荐.getColNo()), "否")) ? 1 : 0);
            dto.setMarketPrice(this.getCellValue(row.getCell(市场价.getColNo()), null) != null
                    ? new BigDecimal(this.getCellValue(row.getCell(市场价.getColNo()), null)).setScale(2, RoundingMode.HALF_UP) : null);
            dto.setProductPrice(new BigDecimal(this.getCellValue(row.getCell(销售价.getColNo()), "0")).setScale(2, RoundingMode.HALF_UP));
            dto.setProductStock(Double.valueOf(this.getCellValue(row.getCell(库存.getColNo()), "1")).intValue());
            dto.setProductStockWarning(Double.valueOf(this.getCellValue(row.getCell(库存预警值.getColNo()), "-1")).intValue());
            dto.setWeight(this.getCellValue(row.getCell(重量.getColNo()), null) != null
                    ? new BigDecimal(this.getCellValue(row.getCell(重量.getColNo()), null)).setScale(3, RoundingMode.HALF_UP) : null);
            dto.setLength(this.getCellValue(row.getCell(长.getColNo()), null) != null
                    ? new BigDecimal(this.getCellValue(row.getCell(长.getColNo()), null)).setScale(3, RoundingMode.HALF_UP) : null);
            dto.setWidth(this.getCellValue(row.getCell(宽.getColNo()), null) != null
                    ? new BigDecimal(this.getCellValue(row.getCell(宽.getColNo()), null)).setScale(3, RoundingMode.HALF_UP) : null);
            dto.setHeight(this.getCellValue(row.getCell(高.getColNo()), null) != null
                    ? new BigDecimal(this.getCellValue(row.getCell(高.getColNo()), null)).setScale(3, RoundingMode.HALF_UP) : null);
            dto.setProductCode(this.getCellValue(row.getCell(货号.getColNo()), null));
            dto.setBarCode(this.getCellValue(row.getCell(条形码.getColNo()), null));
            dto.setGoodsDetails(this.getCellValue(row.getCell(商品详情.getColNo()), null));
            List<GoodsPublishFrontParamDTO.ImageInfo> imageList = new ArrayList<>();
            List<String> goodsImages = goodsImageMap.get(i);
            for (int j = 0; j < goodsImages.size(); j++) {
                GoodsPublishFrontParamDTO.ImageInfo imageInfo = new GoodsPublishFrontParamDTO.ImageInfo();
                if (j == 0) {
                    imageInfo.setIsMain(1);
                }
                imageInfo.setImage(goodsImages.get(j));
                imageList.add(imageInfo);
            }

            dto.setImageList(imageList);
            dto.setGoodsVideo(goodsVideoMap.get(i));
            //构造入库dto
            GoodsPublishInsertDTO insertDTO = new GoodsPublishInsertDTO(dto);

            goodsSellerModel.saveGoods(vendor, insertDTO);
        }
    }

    /**
     * 检测导入文件
     *
     * @param file
     * @param store
     * @param errFileUrlList 如果文件检测出错，将批注好的文件存储路径放入list
     * @return
     */
    private Boolean checkFile(MultipartFile file, Store store, List<String> errFileUrlList,
                              Map<Integer/*rowNo.*/, List<String>/*图片列表，相对地址*/> goodsImageMap,
                              Map<Integer/*rowNo.*/, String/*视频相对地址*/> goodsVideoMap) throws IOException {
        AssertUtil.notNull(file, "请选择要上传的文件");
        AssertUtil.isTrue(StringUtils.isEmpty(file.getOriginalFilename()) || !file.getOriginalFilename().endsWith(".xls"), "文件格式有误");

        //获取excel文件
        Workbook workbook;
        try {
            workbook = new HSSFWorkbook(file.getInputStream());
        } catch (IOException e) {
            throw new MallException("文件格式有误");
        }
        Sheet sheet = workbook.getSheetAt(0);
        Drawing drawingPatriarch = sheet.createDrawingPatriarch();//用于添加备注
        Integer importGoodsNum = sheet.getPhysicalNumberOfRows();
        AssertUtil.isTrue(importGoodsNum <= 1, "导入数据不能为空");
        AssertUtil.isTrue(importGoodsNum.compareTo(MAX_IMPORT_GOODS) > 0, "导入商品不能超过6000条");

        boolean checkPass = true;//是否校验通过,有一条不通过则为不通过
        //逐行校验
        for (int i = 1; i < importGoodsNum; i++) {//第0行为标题，不校验
            Row row = sheet.getRow(i);
            if (row == null) {
                continue;
            }
            goodsImageMap.put(i, new ArrayList<>());
            checkPass = goodsNameCheck(row, drawingPatriarch) && checkPass;
            checkPass = goodsBriefCheck(row, drawingPatriarch) && checkPass;
            checkPass = goodsCategoryCheck(row, drawingPatriarch, store) && checkPass;
            checkPass = freightCheck(row, drawingPatriarch, store) && checkPass;
            checkPass = goodsImageCheck(row, drawingPatriarch, goodsImageMap.get(i)) && checkPass;
            checkPass = goodsVideoCheck(row, drawingPatriarch, i, goodsVideoMap) && checkPass;
            checkPass = marketPriceCheck(row, drawingPatriarch) && checkPass;
            checkPass = goodsPriceCheck(row, drawingPatriarch) && checkPass;
            checkPass = goodsStockCheck(row, drawingPatriarch) && checkPass;
            checkPass = weightCheck(row, drawingPatriarch) && checkPass;
            checkPass = lengthCheck(row, drawingPatriarch) && checkPass;
            checkPass = widthCheck(row, drawingPatriarch) && checkPass;
            checkPass = heightCheck(row, drawingPatriarch) && checkPass;
            checkPass = productStockWarningCheck(row, drawingPatriarch) && checkPass;
            checkPass = productCodeCheck(row, drawingPatriarch) && checkPass;
            checkPass = barCodeCheck(row, drawingPatriarch) && checkPass;
        }

        //校验未通过，将批注后的文件上传
        if (!checkPass) {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            workbook.write(bos);

            HttpResponse execute = HttpRequest.post(DomainUrlUtil.SLD_API_URL + "/v3/oss/common/upload")
                    .header("Content-Type", "multipart/form-data")
                    .form("file", bos.toByteArray(), "错误信息.xls")
                    .form("source", "file")
                    .execute();
            bos.close();
            if (execute.isOk()) {
                errFileUrlList.add(JSON.parseObject(execute.body()).getJSONObject("data").getString("url"));
            } else {
                errFileUrlList.add("");
            }
        }

        return checkPass;
    }

    /**
     * 商品名称检测
     *
     * @param row
     * @param drawingPatriarch
     * @return
     */
    private Boolean goodsNameCheck(Row row, Drawing drawingPatriarch) {
        Integer colNo = 商品名称.getColNo();
        Cell cell = row.getCell(colNo);
        //移除单元格错误批注
        this.removeCellError(cell);

        if (cell == null) {
            cell = row.createCell(colNo);
            //检测不通过，将此单元格背景标红，并增加批注
            this.setErrCell("商品名称必填", cell, drawingPatriarch);
            return false;
        }

        String value = this.getCellValue(cell, null);
        if (StringUtils.isEmpty(value)) {
            //检测不通过，将此单元格背景标红，并增加批注
            this.setErrCell("商品名称必填", cell, drawingPatriarch);
            return false;
        }
        if (value.length() > 50) {
            //检测不通过，将此单元格背景标红，并增加批注
            this.setErrCell("商品名称最多50字", cell, drawingPatriarch);
            return false;
        }

        //检测通过
        return true;
    }

    /**
     * 广告语检测
     *
     * @param row
     * @param drawingPatriarch
     * @return
     */
    private Boolean goodsBriefCheck(Row row, Drawing drawingPatriarch) {
        Integer colNo = 广告语.getColNo();
        Cell cell = row.getCell(colNo);
        //移除单元格错误批注
        this.removeCellError(cell);

        if (cell != null) {
            String value = this.getCellValue(cell, null);
            if (!StringUtils.isEmpty(value) && value.length() > 50) {
                //检测不通过，将此单元格背景标红，并增加批注
                this.setErrCell("商品广告语最多50字", cell, drawingPatriarch);
                return false;
            }
        }
        //检测通过
        return true;
    }

    /**
     * 商品分类检测
     *
     * @param row
     * @param drawingPatriarch
     * @return
     */
    private Boolean goodsCategoryCheck(Row row, Drawing drawingPatriarch, Store store) {
        Integer colNo = 商品分类.getColNo();
        Cell cell = row.getCell(colNo);
        //移除单元格错误批注
        this.removeCellError(cell);

        if (cell == null) {
            cell = row.createCell(colNo);
            //检测不通过，将此单元格背景标红，并增加批注
            this.setErrCell("商品分类必填", cell, drawingPatriarch);
            return false;
        }

        String value = this.getCellValue(cell, null);
        if (StringUtils.isEmpty(value)) {
            //检测不通过，将此单元格背景标红，并增加批注
            this.setErrCell("商品分类必填", cell, drawingPatriarch);
            return false;
        }

        String cateIdStr = value.split(">")[0];//分类id
        if (!cateIdStr.matches("^\\d+(\\.\\d+)?$")) {
            //检测不通过，将此单元格背景标红，并增加批注
            this.setErrCell("分类信息有误，请重新下载模板", cell, drawingPatriarch);
            return false;
        }

        Integer cateId = Integer.valueOf(cateIdStr);
        //查询商品分类是否可用
        GoodsCategory goodsCategory = goodsCategoryModel.getGoodsCategoryByCategoryId(cateId);
        if (goodsCategory == null || goodsCategory.getGrade() != GoodsCategoryConst.CATEGORY_GRADE_3 || !goodsCategory.getState().equals(GoodsCategoryConst.CATEGORY_STATE_1)) {
            //分类无效
            //检测不通过，将此单元格背景标红，并增加批注
            this.setErrCell("分类信息有误，请重新下载模板", cell, drawingPatriarch);
            return false;
        }

        if (store.getIsOwnStore().equals(StoreConst.NO_OWN_STORE)) {
            //入驻店铺，查询是否申请了此分类
            StoreBindCategoryExample example = new StoreBindCategoryExample();
            example.setStoreId(store.getStoreId());
            example.setState(StoreConst.STORE_CATEGORY_STATE_PASS);
            example.setGoodsCategoryId1(cateId);
            List<StoreBindCategory> storeBindCategoryList = storeBindCategoryModel.getStoreBindCategoryList(example, null);
            if (CollectionUtils.isEmpty(storeBindCategoryList)) {
                //检测不通过，将此单元格背景标红，并增加批注
                this.setErrCell("分类未申请，请重新下载模板", cell, drawingPatriarch);
                return false;
            }
        }

        //检测通过
        return true;
    }

    /**
     * 运费检测，运费模板与固定运费
     *
     * @param row
     * @param drawingPatriarch
     * @return
     */
    private Boolean freightCheck(Row row, Drawing drawingPatriarch, Store store) {
        Integer colNo1 = 运费模板.getColNo();
        Integer colNo2 = 固定运费.getColNo();
        Cell cell1 = row.getCell(colNo1);
        Cell cell2 = row.getCell(colNo2);
        //移除单元格错误批注
        this.removeCellError(cell1);
        this.removeCellError(cell2);

        if (cell1 == null && cell2 == null) {
            cell1 = row.createCell(colNo1);
            //不能同时为空
            this.setErrCell("运费模板与固定运费二选一", cell1, drawingPatriarch);
            return false;
        }

        String value = this.getCellValue(cell1, null);
        if (cell1 != null && !StringUtils.isEmpty(value)) {
            //运费模板不为空，优先使用运费模板
            String freightIdStr = value.split(">")[0];
            if (!freightIdStr.matches("^\\d+(\\.\\d+)?$")) {
                this.setErrCell("运费模板信息有误，请重新下载模板", cell1, drawingPatriarch);
                return false;
            }

            Integer freightId = Integer.valueOf(freightIdStr);
            GoodsFreightTemplate goodsFreightTemplate = goodsFreightTemplateModel.getGoodsFreightTemplateByFreightTemplateId(freightId);
            if (goodsFreightTemplate == null || !store.getStoreId().equals(goodsFreightTemplate.getStoreId())) {
                this.setErrCell("运费模板信息有误，请重新下载模板", cell1, drawingPatriarch);
                return false;
            }

            //有运费模板，不校验固定运费
            return true;
        }

        //运费模板为空，使用固定运费
        value = this.getCellValue(cell2, "0");
        if (!value.matches("^\\d+(\\.\\d+)?$")) {
            this.setErrCell("固定运费必须为数字", cell2, drawingPatriarch);
            return false;
        }

        if (value.length() > 10) {
            this.setErrCell("格式有误，长度过长", cell2, drawingPatriarch);
            return false;
        }

        //检测通过
        return true;
    }

    /**
     * 商品图片检测
     *
     * @param row
     * @param drawingPatriarch
     * @return
     */
    private Boolean goodsImageCheck(Row row, Drawing drawingPatriarch, List<String> goodsImages) {
        Integer colNo = 商品图片.getColNo();
        Cell cell = row.getCell(colNo);
        //移除单元格错误批注
        this.removeCellError(cell);

        if (cell == null) {
            cell = row.createCell(colNo);
            this.setErrCell("商品图片必填", cell, drawingPatriarch);
            return false;
        }
        String value = this.getCellValue(cell, null);
        if (StringUtils.isEmpty(value)) {
            this.setErrCell("商品图片必填", cell, drawingPatriarch);
            return false;
        }
        String[] goodsImageArr = value.split(",");
        if (goodsImageArr.length > 6) {
            this.setErrCell("商品图片最多6张", cell, drawingPatriarch);
            return false;
        }

        //图片格式检测
        for (String goodsImage : goodsImageArr) {
            if (StringUtils.isEmpty(goodsImage) || !goodsImage.matches("^(?=^.{3,255}$)(http(s)?://)?(www\\.)?[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+(:\\d+)*(/\\w+\\.\\w+)*(\\w+|\\w+/)+(\\.\\w+)")) {
                this.setErrCell("图片格式有误，请填写图片绝对地址", cell, drawingPatriarch);
                return false;
            }

            //图片后缀检测
            if (!goodsImage.matches("\\S*?(jpg|jpeg|bmp|gif|png|tif)")) {
                this.setErrCell("仅支持jpg|jpeg|bmp|gif|png|tif格式的图片", cell, drawingPatriarch);
                return false;
            }

            //上传图片，并保存相对地址
            try {
                byte[] bytes = this.urlToBytes(goodsImage);
                String imageName = UUID.randomUUID() + goodsImage.substring(goodsImage.lastIndexOf("."));
                HttpResponse execute = HttpRequest.post(DomainUrlUtil.SLD_API_URL + "/v3/oss/common/upload")
                        .header("Content-Type", "multipart/form-data")
                        .form("file", bytes, imageName)
                        .form("source", "goods")
                        .execute();
                if (execute.isOk()) {
                    goodsImages.add(JSON.parseObject(execute.body()).getJSONObject("data").getString("path"));
                } else {
                    this.setErrCell("图片资源不存在", cell, drawingPatriarch);
                    return false;
                }
            } catch (IOException e) {
                this.setErrCell("图片资源不存在", cell, drawingPatriarch);
                return false;
            }
        }

        //检测通过
        return true;
    }

    /**
     * 商品视频检测
     *
     * @param row
     * @param drawingPatriarch
     * @param rowNo            行号
     * @param goodsVideoMap    每行数据对应的商品视频map
     * @return
     */
    private Boolean goodsVideoCheck(Row row, Drawing drawingPatriarch, Integer rowNo, Map<Integer/*rowNo.*/, String/*视频相对地址*/> goodsVideoMap) {
        Integer colNo = 商品视频.getColNo();
        Cell cell = row.getCell(colNo);
        //移除单元格错误批注
        this.removeCellError(cell);

        if (cell == null) {
            return true;
        }
        String value = this.getCellValue(cell, null);
        if (StringUtils.isEmpty(value)) {
            return true;
        }

        //视频格式检测
        if (!value.matches("^(?=^.{3,255}$)(http(s)?://)?(www\\.)?[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+(:\\d+)*(/\\w+\\.\\w+)*(\\w+|\\w+/)+(\\.\\w+)")) {
            this.setErrCell("视频格式有误，请填写视频绝对地址", cell, drawingPatriarch);
            return false;
        }

        //图片后缀检测
        if (!value.matches("\\S*?(avi|mov|rmvb|rm|flv|mp4|3gp)")) {
            this.setErrCell("仅支持avi|mov|rmvb|rm|flv|mp4|3gp格式的视频", cell, drawingPatriarch);
            return false;
        }

        //上传图片，并保存相对地址
        try {
            byte[] bytes = this.urlToBytes(value);
            String videoName = UUID.randomUUID() + value.substring(value.lastIndexOf("."));
            HttpResponse execute = HttpRequest.post(DomainUrlUtil.SLD_API_URL + "/v3/oss/common/upload")
                    .header("Content-Type", "multipart/form-data")
                    .form("file", bytes, videoName)
                    .form("source", "video")
                    .execute();
            if (execute.isOk()) {
                goodsVideoMap.put(rowNo, JSON.parseObject(execute.body()).getJSONObject("data").getString("path"));
            } else {
                this.setErrCell("视频资源不存在", cell, drawingPatriarch);
                return false;
            }
        } catch (IOException e) {
            this.setErrCell("视频资源不存在", cell, drawingPatriarch);
            return false;
        }
        //检测通过
        return true;
    }

    /**
     * 市场价检测
     *
     * @param row
     * @param drawingPatriarch
     * @return
     */
    private Boolean marketPriceCheck(Row row, Drawing drawingPatriarch) {
        Integer colNo = 市场价.getColNo();
        Cell cell = row.getCell(colNo);
        //移除单元格错误批注
        this.removeCellError(cell);

        String value = this.getCellValue(cell, null);
        if (StringUtils.isEmpty(value)) {
            return true;
        }

        if (!value.matches("^\\d+(\\.\\d+)?$")) {
            this.setErrCell("市场价必须为数字", cell, drawingPatriarch);
            return false;
        }

        if (value.length() > 10) {
            this.setErrCell("格式有误，长度过长", cell, drawingPatriarch);
            return false;
        }

        //检测通过
        return true;
    }

    /**
     * 销售价检测
     *
     * @param row
     * @param drawingPatriarch
     * @return
     */
    private Boolean goodsPriceCheck(Row row, Drawing drawingPatriarch) {
        Integer colNo = 销售价.getColNo();
        Cell cell = row.getCell(colNo);
        //移除单元格错误批注
        this.removeCellError(cell);

        if (cell == null) {
            cell = row.createCell(colNo);
            this.setErrCell("销售价必填", cell, drawingPatriarch);
            return false;
        }

        String value = this.getCellValue(cell, null);
        if (StringUtils.isEmpty(value)) {
            this.setErrCell("销售价必填", cell, drawingPatriarch);
            return false;
        }

        if (!value.matches("^\\d+(\\.\\d+)?$")) {
            this.setErrCell("销售价必须为数字", cell, drawingPatriarch);
            return false;
        }

        if (value.length() > 10) {
            this.setErrCell("格式有误，长度过长", cell, drawingPatriarch);
            return false;
        }

        //销售价必须小于市场价
        String marketPriceStr = this.getCellValue(row.getCell(市场价.getColNo()), null);
        if (!StringUtils.isEmpty(marketPriceStr) && new BigDecimal(marketPriceStr).compareTo(new BigDecimal(value)) <= 0) {
            this.setErrCell("销售价必须小于市场价", cell, drawingPatriarch);
            return false;
        }

        //检测通过
        return true;
    }

    /**
     * 库存检测
     *
     * @param row
     * @param drawingPatriarch
     * @return
     */
    private Boolean goodsStockCheck(Row row, Drawing drawingPatriarch) {
        Integer colNo = 库存.getColNo();
        Cell cell = row.getCell(colNo);
        //移除单元格错误批注
        this.removeCellError(cell);

        if (cell == null) {
            cell = row.createCell(colNo);
            this.setErrCell("库存必填", cell, drawingPatriarch);
            return false;
        }

        String value = this.getCellValue(cell, null);
        if (StringUtils.isEmpty(value)) {
            this.setErrCell("库存必填", cell, drawingPatriarch);
            return false;
        }

        if (!value.matches("\\d+") || "0".equals(value)) {
            this.setErrCell("库存必须为正整数", cell, drawingPatriarch);
            return false;
        }

        try {
            Integer.parseInt(value);
        } catch (NumberFormatException e) {
            this.setErrCell("库存必须小于" + Integer.MAX_VALUE, cell, drawingPatriarch);
            return false;
        }

        //检测通过
        return true;
    }

    /**
     * 重量检测
     *
     * @param row
     * @param drawingPatriarch
     * @return
     */
    private Boolean weightCheck(Row row, Drawing drawingPatriarch) {
        Integer colNo = 重量.getColNo();
        Cell cell = row.getCell(colNo);
        //移除单元格错误批注
        this.removeCellError(cell);

        String value = this.getCellValue(cell, null);
        if (StringUtils.isEmpty(value)) {
            return true;
        }

        if (!value.matches("^\\d+(\\.\\d+)?$")) {
            this.setErrCell("重量必须为数字", cell, drawingPatriarch);
            return false;
        }

        if (value.length() > 10) {
            this.setErrCell("格式有误，长度过长", cell, drawingPatriarch);
            return false;
        }

        //检测通过
        return true;
    }

    /**
     * 长检测
     *
     * @param row
     * @param drawingPatriarch
     * @return
     */
    private Boolean lengthCheck(Row row, Drawing drawingPatriarch) {
        Integer colNo = 长.getColNo();
        Cell cell = row.getCell(colNo);
        //移除单元格错误批注
        this.removeCellError(cell);

        String value = this.getCellValue(cell, null);
        if (StringUtils.isEmpty(value)) {
            return true;
        }

        if (!value.matches("^\\d+(\\.\\d+)?$")) {
            this.setErrCell("长必须为数字", cell, drawingPatriarch);
            return false;
        }

        if (value.length() > 10) {
            this.setErrCell("格式有误，长度过长", cell, drawingPatriarch);
            return false;
        }

        //检测通过
        return true;
    }

    /**
     * 宽检测
     *
     * @param row
     * @param drawingPatriarch
     * @return
     */
    private Boolean widthCheck(Row row, Drawing drawingPatriarch) {
        Integer colNo = 宽.getColNo();
        Cell cell = row.getCell(colNo);
        //移除单元格错误批注
        this.removeCellError(cell);

        String value = this.getCellValue(cell, null);
        if (StringUtils.isEmpty(value)) {
            return true;
        }

        if (!value.matches("^\\d+(\\.\\d+)?$")) {
            this.setErrCell("宽必须为数字", cell, drawingPatriarch);
            return false;
        }

        if (value.length() > 10) {
            this.setErrCell("格式有误，长度过长", cell, drawingPatriarch);
            return false;
        }

        //检测通过
        return true;
    }

    /**
     * 高检测
     *
     * @param row
     * @param drawingPatriarch
     * @return
     */
    private Boolean heightCheck(Row row, Drawing drawingPatriarch) {
        Integer colNo = 高.getColNo();
        Cell cell = row.getCell(colNo);
        //移除单元格错误批注
        this.removeCellError(cell);

        String value = this.getCellValue(cell, null);
        if (StringUtils.isEmpty(value)) {
            return true;
        }

        if (!value.matches("^\\d+(\\.\\d+)?$")) {
            this.setErrCell("高必须为数字", cell, drawingPatriarch);
            return false;
        }

        if (value.length() > 10) {
            this.setErrCell("格式有误，长度过长", cell, drawingPatriarch);
            return false;
        }

        //检测通过
        return true;
    }

    /**
     * 库存预警值检测
     *
     * @param row
     * @param drawingPatriarch
     * @return
     */
    private Boolean productStockWarningCheck(Row row, Drawing drawingPatriarch) {
        Integer colNo = 库存预警值.getColNo();
        Cell cell = row.getCell(colNo);
        //移除单元格错误批注
        this.removeCellError(cell);

        String value = this.getCellValue(cell, null);
        if (StringUtils.isEmpty(value)) {
            return true;
        }

        if (!value.matches("\\d+")) {
            this.setErrCell("库存预警值必须为正整数", cell, drawingPatriarch);
            return false;
        }

        try {
            Integer.parseInt(value);
        } catch (NumberFormatException e) {
            this.setErrCell("库存必须小于" + Integer.MAX_VALUE, cell, drawingPatriarch);
            return false;
        }

        //检测通过
        return true;
    }

    /**
     * 货号检测
     *
     * @param row
     * @param drawingPatriarch
     * @return
     */
    private Boolean productCodeCheck(Row row, Drawing drawingPatriarch) {
        Integer colNo = 货号.getColNo();
        Cell cell = row.getCell(colNo);
        //移除单元格错误批注
        this.removeCellError(cell);

        String value = this.getCellValue(cell, null);
        if (StringUtils.isEmpty(value)) {
            return true;
        }

        if (value.length() > 50) {
            this.setErrCell("货号最大50位", cell, drawingPatriarch);
            return false;
        }

        //检测通过
        return true;
    }

    /**
     * 条形码检测
     *
     * @param row
     * @param drawingPatriarch
     * @return
     */
    private Boolean barCodeCheck(Row row, Drawing drawingPatriarch) {
        Integer colNo = 条形码.getColNo();
        Cell cell = row.getCell(colNo);
        //移除单元格错误批注
        this.removeCellError(cell);

        String value = this.getCellValue(cell, null);
        if (StringUtils.isEmpty(value)) {
            return true;
        }

        if (value.length() > 50) {
            this.setErrCell("条形码最大50位", cell, drawingPatriarch);
            return false;
        }

        //检测通过
        return true;
    }

    /**
     * 错误单元格处理，写入批注，设置背景颜色
     *
     * @param errRemark
     * @param cell
     * @param drawingPatriarch
     */
    private void setErrCell(String errRemark, Cell cell, Drawing drawingPatriarch) {
        CellStyle cellStyle = cell.getCellStyle();
        CellStyle newStyle = cell.getRow().getSheet().getWorkbook().createCellStyle();
        newStyle.cloneStyleFrom(cellStyle);
        newStyle.setFillForegroundColor(HSSFColor.RED.index);
        newStyle.setFillPattern(CellStyle.SOLID_FOREGROUND);
        cell.setCellStyle(newStyle);
        Comment cellComment = drawingPatriarch.createCellComment(new HSSFClientAnchor());
        cellComment.setString(new HSSFRichTextString(errRemark));
        cell.setCellComment(cellComment);
    }

    /**
     * 去除单元格错误标注，去除错误背景色
     *
     * @param cell
     */
    private void removeCellError(Cell cell) {
        if (cell == null) {
            return;
        }
        CellStyle cellStyle = cell.getCellStyle();
        CellStyle newStyle = cell.getRow().getSheet().getWorkbook().createCellStyle();
        newStyle.cloneStyleFrom(cellStyle);
        newStyle.setFillForegroundColor(HSSFColor.AUTOMATIC.index);
        cell.setCellStyle(newStyle);
        cell.removeCellComment();
    }

    /**
     * 将url转为字节流
     *
     * @param urlStr
     * @return
     * @throws IOException
     */
    private byte[] urlToBytes(String urlStr) throws IOException {
        URL url = new URL(urlStr);
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod("GET");
        con.setConnectTimeout(4 * 1000);
        InputStream inStream = con.getInputStream(); //通过输入流获取图片数据
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[2048];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outStream.write(buffer, 0, len);
        }
        inStream.close();
        return outStream.toByteArray();
    }

    /**
     * 运费模板下拉菜单
     *
     * @param helper
     * @param store
     * @return
     */
    private DataValidation getFreightValidation(DataValidationHelper helper, Store store, Workbook workbook) {
        GoodsFreightTemplateExample example = new GoodsFreightTemplateExample();
        example.setStoreId(store.getStoreId());
        List<GoodsFreightTemplate> list = goodsFreightTemplateModel.getGoodsFreightTemplateList(example, null);
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }
        List<String/*freightId>freightName*/> freightNameList = new ArrayList<>();
        list.forEach(goodsFreightTemplate -> {
            freightNameList.add(goodsFreightTemplate.getFreightTemplateId() + ">" + goodsFreightTemplate.getTemplateName());
        });
        //创建分类sheet，用于数据引用
        Sheet freight = workbook.createSheet("freight");
        //隐藏sheet
        workbook.setSheetHidden(2, true);
        //将分类数据写入sheet的第一列
        for (int i = 0; i < freightNameList.size(); i++) {
            Row row = freight.createRow(i);
            Cell cell = row.createCell(0);
            cell.setCellValue(freightNameList.get(i));
        }
        //创建引用域
        Name namedCell = workbook.createName();
        namedCell.setNameName("freight");
        namedCell.setRefersToFormula("freight!$A$1:$A$" + freightNameList.size());//引用freight sheet
        DataValidationConstraint explicitListConstraint = helper.createFormulaListConstraint("freight");
        return helper.createValidation(explicitListConstraint, new CellRangeAddressList(1, MAX_IMPORT_GOODS + 1, 运费模板.getColNo(), 运费模板.getColNo()));
    }

    /**
     * 商品分类下拉菜单
     *
     * @param helper
     * @param store
     * @return
     */
    private DataValidation getGoodsCategoryValidation(DataValidationHelper helper, Store store, Workbook workbook) {
        //查询店铺拥有的三级分类，格式为 categoryId3>三级分类路径
        List<String/*categoryId3->三级分类路径*/> catePathList = new ArrayList<>();
        if (store.getIsOwnStore().equals(StoreConst.IS_OWN_STORE)) {
            Map<Integer/*cateId*/, GoodsCategory> goodsCategoryMap = new HashMap<>();
            //自营店铺，返回所有分类
            GoodsCategoryExample goodsCategoryExample = new GoodsCategoryExample();
            goodsCategoryExample.setState(GoodsCategoryConst.CATEGORY_STATE_1);
            goodsCategoryExample.setGrade(GoodsCategoryConst.CATEGORY_GRADE_3);
            goodsCategoryExample.setOrderBy("sort asc");
            List<GoodsCategory> thirdCategory = goodsCategoryModel.getGoodsCategoryList(goodsCategoryExample, null);
            AssertUtil.notEmpty(thirdCategory, "平台未添加商品分类");
            thirdCategory.forEach(goodsCategory -> {
                StringBuilder catePath = new StringBuilder();
                catePath.append(">").append(goodsCategory.getCategoryName());
                //查询二级分类
                GoodsCategory goodsCategory2;
                if (goodsCategoryMap.containsKey(goodsCategory.getPid())) {
                    goodsCategory2 = goodsCategoryMap.get(goodsCategory.getPid());
                } else {
                    goodsCategory2 = goodsCategoryModel.getGoodsCategoryByCategoryId(goodsCategory.getPid());
                    goodsCategoryMap.put(goodsCategory2.getCategoryId(), goodsCategory2);
                }
                catePath.insert(0, ">" + goodsCategory2.getCategoryName());
                //查询一级分类
                GoodsCategory goodsCategory1;
                if (goodsCategoryMap.containsKey(goodsCategory2.getPid())) {
                    goodsCategory1 = goodsCategoryMap.get(goodsCategory2.getPid());
                } else {
                    goodsCategory1 = goodsCategoryModel.getGoodsCategoryByCategoryId(goodsCategory2.getPid());
                    goodsCategoryMap.put(goodsCategory1.getCategoryId(), goodsCategory1);
                }
                catePath.insert(0, ">" + goodsCategory1.getCategoryName());
                catePath.insert(0, goodsCategory.getCategoryId());

                catePathList.add(catePath.toString());
            });
        } else {
            //非自营，查询申请通过的三级
            StoreBindCategoryExample example = new StoreBindCategoryExample();
            example.setStoreId(store.getStoreId());
            example.setState(StoreConst.STORE_CATEGORY_STATE_PASS);
            example.setOrderBy("create_time desc");
            List<StoreBindCategory> storeBindCategoryList = storeBindCategoryModel.getStoreBindCategoryList(example, null);
            AssertUtil.notEmpty(storeBindCategoryList, "请先申请商品分类");
            storeBindCategoryList.forEach(storeBindCategory -> {
                catePathList.add(storeBindCategory.getGoodsCategoryId1() + ">" + storeBindCategory.getGoodsCateName());
            });
        }
        //创建分类sheet，用于数据引用
        Sheet category = workbook.createSheet("category");
        //隐藏sheet
        workbook.setSheetHidden(1, true);
        //将分类数据写入sheet的第一列
        for (int i = 0; i < catePathList.size(); i++) {
            Row row = category.createRow(i);
            Cell cell = row.createCell(0);
            cell.setCellValue(catePathList.get(i));
        }
        //创建引用域
        Name namedCell = workbook.createName();
        namedCell.setNameName("category");
        namedCell.setRefersToFormula("category!$A$1:$A$" + catePathList.size());//引用category sheet
        DataValidationConstraint explicitListConstraint = helper.createFormulaListConstraint("category");
        return helper.createValidation(explicitListConstraint, new CellRangeAddressList(1, MAX_IMPORT_GOODS + 1, 商品分类.getColNo(), 商品分类.getColNo()));
    }

    /**
     * 获取表头单元格样式
     * 居中，粗体
     *
     * @param book
     * @return
     */
    private CellStyle getTitleStyle(Workbook book) {
        Font fontStyle = book.createFont();
        fontStyle.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
        CellStyle cellStyle = book.createCellStyle();
        cellStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER);//水平居中
        cellStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//垂直居中
        cellStyle.setFont(fontStyle);
        return cellStyle;
    }


    /**
     * 获取单元格值，均已字符串返回
     *
     * @param cell         单元格，为空时返回默认值
     * @param defaultValue 默认值
     * @return
     */
    private String getCellValue(Cell cell, String defaultValue) {
        if (cell == null) {
            return defaultValue;
        }
        switch (cell.getCellType()) {
            case CELL_TYPE_BLANK:
                //空
                return defaultValue;
            case CELL_TYPE_NUMERIC:
                //数字
                return String.valueOf(cell.getNumericCellValue()).replaceFirst("[.]0+", "");
            case CELL_TYPE_STRING://字符串
            case CELL_TYPE_FORMULA://公式
                return StringUtils.isEmpty(cell.getStringCellValue()) ? defaultValue : cell.getStringCellValue();
            case CELL_TYPE_BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case CELL_TYPE_ERROR:
                return String.valueOf(cell.getErrorCellValue());
            default:
                return defaultValue;
        }
    }
}
