|
|
@@ -0,0 +1,655 @@
|
|
|
+package shop.alien.storeplatform.service.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.collection.CollectionUtil;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.poi.ss.usermodel.*;
|
|
|
+import org.apache.poi.xssf.usermodel.*;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import org.springframework.web.multipart.MultipartFile;
|
|
|
+import shop.alien.entity.result.R;
|
|
|
+import shop.alien.entity.store.StoreImg;
|
|
|
+import shop.alien.entity.store.excelVo.util.ExcelImage;
|
|
|
+import shop.alien.mapper.StoreImgMapper;
|
|
|
+import shop.alien.storeplatform.entity.StorePlatformBathFacility;
|
|
|
+import shop.alien.storeplatform.entity.StorePlatformStoreMenu;
|
|
|
+import shop.alien.storeplatform.feign.AlienStoreFeign;
|
|
|
+import shop.alien.storeplatform.mapper.StorePlatformBathFacilityServiceMapper;
|
|
|
+import shop.alien.storeplatform.service.StorePlatformBathFacilityService;
|
|
|
+import shop.alien.storeplatform.vo.StorePlatformBathFacilityImportVo;
|
|
|
+
|
|
|
+import java.io.ByteArrayInputStream;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.lang.reflect.Field;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 商户平台-洗浴设施及服务服务实现类
|
|
|
+ *
|
|
|
+ * @author system
|
|
|
+ * @since 2025-01-XX
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class StorePlatformBathFacilityServiceImpl extends ServiceImpl<StorePlatformBathFacilityServiceMapper, StorePlatformBathFacility> implements StorePlatformBathFacilityService {
|
|
|
+
|
|
|
+ private final StorePlatformBathFacilityServiceMapper facilityMapper;
|
|
|
+
|
|
|
+ private final AlienStoreFeign alienStoreFeign;
|
|
|
+
|
|
|
+ private final StoreImgMapper storeImgMapper;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public StorePlatformBathFacility getById(Integer id) {
|
|
|
+ log.info("StorePlatformBathFacilityServiceImpl.getById id={}", id);
|
|
|
+ if (id == null) {
|
|
|
+ throw new RuntimeException("设施ID不能为空");
|
|
|
+ }
|
|
|
+ StorePlatformBathFacility facility = super.getById(id);
|
|
|
+ List<StoreImg> imgList = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>()
|
|
|
+ .eq(StoreImg::getStoreId, facility.getStoreId()).eq(StoreImg::getImgType, 29).in(StoreImg::getBusinessId, facility.getId()));
|
|
|
+
|
|
|
+ if (CollectionUtil.isNotEmpty(imgList)) {
|
|
|
+ facility.setImgUrl(imgList.get(0).getImgUrl());
|
|
|
+ }
|
|
|
+ return facility;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<StorePlatformBathFacility> getListByStoreId(Integer storeId) {
|
|
|
+ log.info("StorePlatformBathFacilityServiceImpl.getListByStoreId storeId={}", storeId);
|
|
|
+ if (storeId == null) {
|
|
|
+ throw new RuntimeException("门店ID不能为空");
|
|
|
+ }
|
|
|
+ LambdaQueryWrapper<StorePlatformBathFacility> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(StorePlatformBathFacility::getStoreId, storeId)
|
|
|
+ .eq(StorePlatformBathFacility::getDeleteFlag, 0)
|
|
|
+ .orderByAsc(StorePlatformBathFacility::getFacilityCategory)
|
|
|
+ .orderByAsc(StorePlatformBathFacility::getId);
|
|
|
+
|
|
|
+ List<StorePlatformBathFacility> list = this.list(queryWrapper);
|
|
|
+
|
|
|
+ // 查询图片
|
|
|
+ List<Integer> ids = list.stream().map(StorePlatformBathFacility::getId).collect(Collectors.toList());
|
|
|
+ List<StoreImg> imgList = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>()
|
|
|
+ .eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 29).in(StoreImg::getBusinessId, ids));
|
|
|
+
|
|
|
+ if (CollectionUtil.isNotEmpty(imgList)) {
|
|
|
+ list.forEach(facility -> {
|
|
|
+ imgList.stream()
|
|
|
+ .filter(img -> img.getBusinessId().equals(facility.getId()))
|
|
|
+ .findFirst()
|
|
|
+ .ifPresent(img -> facility.setImgUrl(img.getImgUrl()));
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<StorePlatformBathFacility> getListByStoreIdAndCategory(Integer storeId, Integer facilityCategory) {
|
|
|
+ log.info("StorePlatformBathFacilityServiceImpl.getListByStoreIdAndCategory storeId={}, facilityCategory={}", storeId, facilityCategory);
|
|
|
+ if (storeId == null) {
|
|
|
+ throw new RuntimeException("门店ID不能为空");
|
|
|
+ }
|
|
|
+ LambdaQueryWrapper<StorePlatformBathFacility> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(StorePlatformBathFacility::getStoreId, storeId)
|
|
|
+ .eq(StorePlatformBathFacility::getDeleteFlag, 0);
|
|
|
+ if (facilityCategory != null) {
|
|
|
+ queryWrapper.eq(StorePlatformBathFacility::getFacilityCategory, facilityCategory);
|
|
|
+ }
|
|
|
+ queryWrapper.orderByAsc(StorePlatformBathFacility::getFacilityCategory)
|
|
|
+ .orderByAsc(StorePlatformBathFacility::getId);
|
|
|
+
|
|
|
+ List<StorePlatformBathFacility> list = this.list(queryWrapper);
|
|
|
+
|
|
|
+ // 查询图片
|
|
|
+ List<Integer> ids = list.stream().map(StorePlatformBathFacility::getId).collect(Collectors.toList());
|
|
|
+ List<StoreImg> imgList = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>()
|
|
|
+ .eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 29).in(StoreImg::getBusinessId, ids));
|
|
|
+
|
|
|
+ if (CollectionUtil.isNotEmpty(imgList)) {
|
|
|
+ list.forEach(facility -> {
|
|
|
+ imgList.stream()
|
|
|
+ .filter(img -> img.getBusinessId().equals(facility.getId()))
|
|
|
+ .findFirst()
|
|
|
+ .ifPresent(img -> facility.setImgUrl(img.getImgUrl()));
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<StorePlatformBathFacility> saveFacility(StorePlatformBathFacility facility) {
|
|
|
+ log.info("StorePlatformBathFacilityServiceImpl.saveFacility facility={}", facility);
|
|
|
+ if (facility == null) {
|
|
|
+ return R.fail("设施信息不能为空");
|
|
|
+ }
|
|
|
+ if (facility.getStoreId() == null) {
|
|
|
+ return R.fail("门店ID不能为空");
|
|
|
+ }
|
|
|
+ if (StringUtils.isEmpty(facility.getFacilityName())) {
|
|
|
+ return R.fail("名称不能为空");
|
|
|
+ }
|
|
|
+ if (facility.getFacilityCategory() == null) {
|
|
|
+ return R.fail("区域分类不能为空");
|
|
|
+ }
|
|
|
+ // 如果使用时间类型为选择时间,则必须填写开始和结束时间
|
|
|
+ if (facility.getUsageTimeType() != null && facility.getUsageTimeType() == 1) {
|
|
|
+ if (StringUtils.isEmpty(facility.getUsageStartTime()) || StringUtils.isEmpty(facility.getUsageEndTime())) {
|
|
|
+ return R.fail("使用时间类型为选择时间时,开始时间和结束时间不能为空");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ boolean flag = this.save(facility);
|
|
|
+ if (!flag) {
|
|
|
+ return R.fail("新增设施失败");
|
|
|
+ }
|
|
|
+ return R.data(facility);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<StorePlatformBathFacility> updateFacility(StorePlatformBathFacility facility) {
|
|
|
+ log.info("StorePlatformBathFacilityServiceImpl.updateFacility facility={}", facility);
|
|
|
+ if (facility == null || facility.getId() == null) {
|
|
|
+ return R.fail("设施信息或设施ID不能为空");
|
|
|
+ }
|
|
|
+ if (StringUtils.isEmpty(facility.getFacilityName())) {
|
|
|
+ return R.fail("名称不能为空");
|
|
|
+ }
|
|
|
+ if (facility.getFacilityCategory() == null) {
|
|
|
+ return R.fail("区域分类不能为空");
|
|
|
+ }
|
|
|
+ // 如果使用时间类型为选择时间,则必须填写开始和结束时间
|
|
|
+ if (facility.getUsageTimeType() != null && facility.getUsageTimeType() == 1) {
|
|
|
+ if (StringUtils.isEmpty(facility.getUsageStartTime()) || StringUtils.isEmpty(facility.getUsageEndTime())) {
|
|
|
+ return R.fail("使用时间类型为选择时间时,开始时间和结束时间不能为空");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ boolean flag = this.updateById(facility);
|
|
|
+ if (!flag) {
|
|
|
+ return R.fail("修改设施失败");
|
|
|
+ }
|
|
|
+ return R.data(facility);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<StorePlatformBathFacility> saveOrUpdateFacility(StorePlatformBathFacility facility) {
|
|
|
+ log.info("StorePlatformBathFacilityServiceImpl.saveOrUpdateFacility facility={}", facility);
|
|
|
+ if (facility == null) {
|
|
|
+ return R.fail("设施信息不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (facility.getId() != null) {
|
|
|
+ // 修改
|
|
|
+ return updateFacility(facility);
|
|
|
+ } else {
|
|
|
+ // 新增
|
|
|
+ return saveFacility(facility);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<String> deleteFacility(Integer id) {
|
|
|
+ log.info("StorePlatformBathFacilityServiceImpl.deleteFacility id={}", id);
|
|
|
+ if (id == null) {
|
|
|
+ return R.fail("设施ID不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ boolean flag = this.removeById(id);
|
|
|
+ if (!flag) {
|
|
|
+ return R.fail("删除设施失败");
|
|
|
+ }
|
|
|
+ return R.success("删除设施成功");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<String> deleteFacilityBatch(List<Integer> ids) {
|
|
|
+ log.info("StorePlatformBathFacilityServiceImpl.deleteFacilityBatch ids={}", ids);
|
|
|
+ if (CollectionUtil.isEmpty(ids)) {
|
|
|
+ return R.fail("设施ID列表不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ boolean flag = this.removeByIds(ids);
|
|
|
+ if (!flag) {
|
|
|
+ return R.fail("批量删除设施失败");
|
|
|
+ }
|
|
|
+ return R.success("批量删除设施成功");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<String> importFacilityFromExcel(MultipartFile file, Integer storeId) {
|
|
|
+ log.info("StorePlatformBathFacilityServiceImpl.importFacilityFromExcel storeId={}", storeId);
|
|
|
+
|
|
|
+ if (file == null || file.isEmpty()) {
|
|
|
+ return R.fail("上传文件为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ String fileName = file.getOriginalFilename();
|
|
|
+ if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
|
|
|
+ return R.fail("文件格式不正确,请上传Excel文件");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (storeId == null) {
|
|
|
+ return R.fail("门店ID不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ List<String> errorMessages = new ArrayList<>();
|
|
|
+ int successCount = 0;
|
|
|
+ int totalCount = 0;
|
|
|
+
|
|
|
+ try (InputStream inputStream = file.getInputStream();
|
|
|
+ XSSFWorkbook workbook = new XSSFWorkbook(inputStream)) {
|
|
|
+ Sheet sheet = workbook.getSheetAt(0);
|
|
|
+
|
|
|
+ // 获取表头(假设表头在第5行,索引为5)
|
|
|
+ Row headerRow = sheet.getRow(5);
|
|
|
+ if (headerRow == null) {
|
|
|
+ return R.fail("Excel文件格式不正确,缺少表头");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建字段映射(表头名称 -> 列索引)
|
|
|
+ Map<String, Integer> headerMap = new HashMap<>();
|
|
|
+ Field[] fields = StorePlatformBathFacilityImportVo.class.getDeclaredFields();
|
|
|
+ for (int i = 0; i < headerRow.getLastCellNum(); i++) {
|
|
|
+ Cell cell = headerRow.getCell(i);
|
|
|
+ if (cell != null) {
|
|
|
+ String headerName = getCellValueAsString(cell);
|
|
|
+ if (StringUtils.isNotEmpty(headerName)) {
|
|
|
+ headerMap.put(headerName.trim(), i);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取图片映射(行索引 -> 图片字节数组)
|
|
|
+ Map<Integer, byte[]> imageMap = extractImagesFromSheet(sheet);
|
|
|
+
|
|
|
+ // 读取数据行(从第6行开始,索引为6)
|
|
|
+ for (int rowIndex = 6; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
|
|
|
+ Row row = sheet.getRow(rowIndex);
|
|
|
+ if (row == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否为空行
|
|
|
+ boolean isEmptyRow = true;
|
|
|
+ for (int i = 0; i < row.getLastCellNum(); i++) {
|
|
|
+ Cell cell = row.getCell(i);
|
|
|
+ if (cell != null && cell.getCellType() != CellType.BLANK) {
|
|
|
+ String cellValue = getCellValueAsString(cell);
|
|
|
+ if (StringUtils.isNotEmpty(cellValue)) {
|
|
|
+ isEmptyRow = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (isEmptyRow) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ totalCount++;
|
|
|
+ StorePlatformBathFacilityImportVo excelVo = new StorePlatformBathFacilityImportVo();
|
|
|
+
|
|
|
+ // 读取每个字段
|
|
|
+ for (Field field : fields) {
|
|
|
+ field.setAccessible(true);
|
|
|
+ String fieldName = field.getName();
|
|
|
+ Integer colIndex = null;
|
|
|
+
|
|
|
+ // 根据字段名查找对应的表头
|
|
|
+ for (Map.Entry<String, Integer> entry : headerMap.entrySet()) {
|
|
|
+ String headerName = entry.getKey();
|
|
|
+ if (isFieldMatch(fieldName, headerName)) {
|
|
|
+ colIndex = entry.getValue();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (colIndex == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ Cell cell = row.getCell(colIndex);
|
|
|
+ if (cell == null && !field.isAnnotationPresent(ExcelImage.class)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (field.isAnnotationPresent(ExcelImage.class)) {
|
|
|
+ // 处理图片字段
|
|
|
+ byte[] imageBytes = imageMap.get(rowIndex);
|
|
|
+ if (imageBytes != null && imageBytes.length > 0) {
|
|
|
+ String imageName = "bathFacility_" + storeId + "_" + System.currentTimeMillis() + "_" + rowIndex + ".jpg";
|
|
|
+ MultipartFile multipartFile = new ByteArrayMultipartFile(imageBytes, imageName);
|
|
|
+ JSONObject jsonObject = alienStoreFeign.uploadFile(multipartFile);
|
|
|
+ if (200 == jsonObject.getIntValue("code")) {
|
|
|
+ field.set(excelVo, jsonObject.getJSONArray("data").get(0));
|
|
|
+ } else {
|
|
|
+ field.set(excelVo, "");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 处理普通字段
|
|
|
+ String cellValue = getCellValueAsString(cell);
|
|
|
+ if (StringUtils.isNotEmpty(cellValue)) {
|
|
|
+ setFieldValue(excelVo, field, cellValue.trim());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("读取字段{}失败:{}", fieldName, e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理导入数据
|
|
|
+ try {
|
|
|
+ validateAndSaveFacility(excelVo, storeId, rowIndex + 1);
|
|
|
+ successCount++;
|
|
|
+ } catch (Exception e) {
|
|
|
+ errorMessages.add(String.format("第%d行:%s", rowIndex + 1, e.getMessage()));
|
|
|
+ log.error("导入第{}行数据失败", rowIndex + 1, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("导入Excel失败", e);
|
|
|
+ return R.fail("导入失败:" + e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建返回消息
|
|
|
+ StringBuilder message = new StringBuilder();
|
|
|
+ message.append(String.format("导入完成:成功%d条,失败%d条", successCount, totalCount - successCount));
|
|
|
+ if (!errorMessages.isEmpty()) {
|
|
|
+ message.append("\n失败详情:\n");
|
|
|
+ for (int i = 0; i < Math.min(errorMessages.size(), 10); i++) {
|
|
|
+ message.append(errorMessages.get(i)).append("\n");
|
|
|
+ }
|
|
|
+ if (errorMessages.size() > 10) {
|
|
|
+ message.append("...还有").append(errorMessages.size() - 10).append("条错误信息");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return R.success(message.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断字段名是否匹配表头
|
|
|
+ */
|
|
|
+ private boolean isFieldMatch(String fieldName, String headerName) {
|
|
|
+ Map<String, String> fieldMapping = new HashMap<>();
|
|
|
+ fieldMapping.put("facilityCategory", "区域(洗浴区/汗蒸区/休闲区/餐饮区)");
|
|
|
+ fieldMapping.put("facilityName", "名称");
|
|
|
+ fieldMapping.put("img", "图片");
|
|
|
+ fieldMapping.put("chargingStandard", "收费标准(元)");
|
|
|
+ fieldMapping.put("usageTimeType", "开放时间(全天/自定义时间)");
|
|
|
+ fieldMapping.put("usageStartTime", "自定义开始时间");
|
|
|
+ fieldMapping.put("usageEndTime", "自定义结束时间");
|
|
|
+ fieldMapping.put("displayInStoreDetail", "状态(展示/隐藏)");
|
|
|
+
|
|
|
+ String expectedHeader = fieldMapping.get(fieldName);
|
|
|
+ return expectedHeader != null && expectedHeader.equals(headerName);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置字段值
|
|
|
+ */
|
|
|
+ private void setFieldValue(StorePlatformBathFacilityImportVo excelVo, Field field, String cellValue) throws Exception {
|
|
|
+ Class<?> fieldType = field.getType();
|
|
|
+ String fieldName = field.getName();
|
|
|
+
|
|
|
+ if (fieldType == Integer.class) {
|
|
|
+ if ("facilityCategory".equals(fieldName)) {
|
|
|
+ // 处理区域分类:洗浴区->1, 汗蒸区->2, 休闲区->3, 餐饮区->4
|
|
|
+ String trimmedValue = cellValue.trim();
|
|
|
+ if ("洗浴区".equals(trimmedValue)) {
|
|
|
+ field.set(excelVo, 1);
|
|
|
+ } else if ("汗蒸区".equals(trimmedValue)) {
|
|
|
+ field.set(excelVo, 2);
|
|
|
+ } else if ("休闲区".equals(trimmedValue)) {
|
|
|
+ field.set(excelVo, 3);
|
|
|
+ } else if ("餐饮区".equals(trimmedValue)) {
|
|
|
+ field.set(excelVo, 4);
|
|
|
+ } else {
|
|
|
+ throw new RuntimeException("区域分类格式错误,请输入'洗浴区'、'汗蒸区'、'休闲区'或'餐饮区'");
|
|
|
+ }
|
|
|
+ } else if ("usageTimeType".equals(fieldName)) {
|
|
|
+ // 处理开放时间:全天->0, 自定义时间->1
|
|
|
+ String trimmedValue = cellValue.trim();
|
|
|
+ if ("全天".equals(trimmedValue)) {
|
|
|
+ field.set(excelVo, 0);
|
|
|
+ } else if ("自定义时间".equals(trimmedValue)) {
|
|
|
+ field.set(excelVo, 1);
|
|
|
+ } else {
|
|
|
+ throw new RuntimeException("开放时间格式错误,请输入'全天'或'自定义时间'");
|
|
|
+ }
|
|
|
+ } else if ("displayInStoreDetail".equals(fieldName)) {
|
|
|
+ // 处理状态:展示->1, 隐藏->0
|
|
|
+ String trimmedValue = cellValue.trim();
|
|
|
+ if ("展示".equals(trimmedValue)) {
|
|
|
+ field.set(excelVo, 1);
|
|
|
+ } else if ("隐藏".equals(trimmedValue)) {
|
|
|
+ field.set(excelVo, 0);
|
|
|
+ } else {
|
|
|
+ throw new RuntimeException("状态字段格式错误,请输入'展示'或'隐藏'");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 其他Integer字段直接解析
|
|
|
+ try {
|
|
|
+ field.set(excelVo, Integer.parseInt(cellValue));
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ throw new RuntimeException(fieldName + "字段格式错误,请输入数字");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // String类型
|
|
|
+ field.set(excelVo, cellValue);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验并保存设施
|
|
|
+ */
|
|
|
+ private void validateAndSaveFacility(StorePlatformBathFacilityImportVo excelVo, Integer storeId, int rowNum) {
|
|
|
+ // 校验必填字段
|
|
|
+ if (StringUtils.isEmpty(excelVo.getFacilityName())) {
|
|
|
+ throw new RuntimeException("名称不能为空");
|
|
|
+ }
|
|
|
+ if (excelVo.getFacilityCategory() == null) {
|
|
|
+ throw new RuntimeException("区域分类不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 校验开放时间和自定义时间段
|
|
|
+ Integer usageTimeType = excelVo.getUsageTimeType() != null ? excelVo.getUsageTimeType() : 0;
|
|
|
+ String usageStartTime = excelVo.getUsageStartTime();
|
|
|
+ String usageEndTime = excelVo.getUsageEndTime();
|
|
|
+
|
|
|
+ if (usageTimeType == 0) {
|
|
|
+ // 全天:自定义开始时间和结束时间应该为空
|
|
|
+ if (StringUtils.isNotEmpty(usageStartTime) || StringUtils.isNotEmpty(usageEndTime)) {
|
|
|
+ throw new RuntimeException("开放时间为'全天'时,自定义开始时间和结束时间不能填写");
|
|
|
+ }
|
|
|
+ } else if (usageTimeType == 1) {
|
|
|
+ // 自定义时间:自定义开始时间和结束时间必须填写
|
|
|
+ if (StringUtils.isEmpty(usageStartTime)) {
|
|
|
+ throw new RuntimeException("开放时间为'自定义时间'时,自定义开始时间不能为空");
|
|
|
+ }
|
|
|
+ if (StringUtils.isEmpty(usageEndTime)) {
|
|
|
+ throw new RuntimeException("开放时间为'自定义时间'时,自定义结束时间不能为空");
|
|
|
+ }
|
|
|
+ // 校验时间格式:HH:mm
|
|
|
+ if (!isValidTimeFormat(usageStartTime)) {
|
|
|
+ throw new RuntimeException("自定义开始时间格式错误,请输入'HH:mm'格式,如'16:00'");
|
|
|
+ }
|
|
|
+ if (!isValidTimeFormat(usageEndTime)) {
|
|
|
+ throw new RuntimeException("自定义结束时间格式错误,请输入'HH:mm'格式,如'18:00'");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建StorePlatformBathFacility对象
|
|
|
+ StorePlatformBathFacility facility = new StorePlatformBathFacility();
|
|
|
+ facility.setStoreId(storeId);
|
|
|
+ facility.setFacilityCategory(excelVo.getFacilityCategory());
|
|
|
+ facility.setFacilityName(excelVo.getFacilityName());
|
|
|
+ facility.setChargingStandard(excelVo.getChargingStandard());
|
|
|
+ facility.setUsageTimeType(usageTimeType);
|
|
|
+ facility.setDisplayInStoreDetail(excelVo.getDisplayInStoreDetail() != null ? excelVo.getDisplayInStoreDetail() : 0);
|
|
|
+
|
|
|
+ // 设置开始和结束时间
|
|
|
+ if (usageTimeType == 1) {
|
|
|
+ facility.setUsageStartTime(usageStartTime);
|
|
|
+ facility.setUsageEndTime(usageEndTime);
|
|
|
+ } else {
|
|
|
+ facility.setUsageStartTime(null);
|
|
|
+ facility.setUsageEndTime(null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存设施
|
|
|
+ boolean flag = this.save(facility);
|
|
|
+
|
|
|
+ // 处理图片
|
|
|
+ if (StringUtils.isNotEmpty(excelVo.getImg())) {
|
|
|
+ StoreImg storeImg = new StoreImg();
|
|
|
+ storeImg.setStoreId(storeId);
|
|
|
+ storeImg.setImgType(29); // 菜单图片类型
|
|
|
+ storeImg.setImgUrl(excelVo.getImg());
|
|
|
+ storeImg.setBusinessId(facility.getId());
|
|
|
+ storeImgMapper.insert(storeImg);
|
|
|
+ }
|
|
|
+ if (!flag) {
|
|
|
+ throw new RuntimeException("保存设施失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验时间格式是否为 HH:mm
|
|
|
+ */
|
|
|
+ private boolean isValidTimeFormat(String time) {
|
|
|
+ if (StringUtils.isEmpty(time)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // 格式:HH:mm (00:00-23:59)
|
|
|
+ String timePattern = "^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$";
|
|
|
+ return time.matches(timePattern);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取单元格值(字符串格式)
|
|
|
+ */
|
|
|
+ private String getCellValueAsString(Cell cell) {
|
|
|
+ if (cell == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (cell.getCellType()) {
|
|
|
+ case STRING:
|
|
|
+ return cell.getStringCellValue();
|
|
|
+ case NUMERIC:
|
|
|
+ if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
|
|
|
+ return cell.getDateCellValue().toString();
|
|
|
+ } else {
|
|
|
+ // 处理数字,避免科学计数法
|
|
|
+ double numericValue = cell.getNumericCellValue();
|
|
|
+ if (numericValue == (long) numericValue) {
|
|
|
+ return String.valueOf((long) numericValue);
|
|
|
+ } else {
|
|
|
+ return String.valueOf(numericValue);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ case BOOLEAN:
|
|
|
+ return String.valueOf(cell.getBooleanCellValue());
|
|
|
+ case FORMULA:
|
|
|
+ return cell.getCellFormula();
|
|
|
+ default:
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从Sheet中提取图片
|
|
|
+ */
|
|
|
+ private Map<Integer, byte[]> extractImagesFromSheet(Sheet sheet) {
|
|
|
+ Map<Integer, byte[]> imageMap = new HashMap<>();
|
|
|
+ if (sheet instanceof XSSFSheet) {
|
|
|
+ XSSFSheet xssfSheet = (XSSFSheet) sheet;
|
|
|
+ XSSFDrawing drawing = xssfSheet.getDrawingPatriarch();
|
|
|
+ if (drawing != null) {
|
|
|
+ List<XSSFShape> shapes = drawing.getShapes();
|
|
|
+ for (XSSFShape shape : shapes) {
|
|
|
+ if (shape instanceof XSSFPicture) {
|
|
|
+ XSSFPicture picture = (XSSFPicture) shape;
|
|
|
+ XSSFClientAnchor anchor = (XSSFClientAnchor) picture.getAnchor();
|
|
|
+ int rowIndex = anchor.getRow1();
|
|
|
+ try {
|
|
|
+ // 直接使用 getData() 方法获取图片字节数组
|
|
|
+ byte[] imageBytes = picture.getPictureData().getData();
|
|
|
+ imageMap.put(rowIndex, imageBytes);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("提取第{}行图片失败:{}", rowIndex, e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return imageMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 临时MultipartFile实现类,用于上传字节数组
|
|
|
+ */
|
|
|
+ private static class ByteArrayMultipartFile implements MultipartFile {
|
|
|
+ private final byte[] content;
|
|
|
+ private final String fileName;
|
|
|
+
|
|
|
+ public ByteArrayMultipartFile(byte[] content, String fileName) {
|
|
|
+ this.content = content;
|
|
|
+ this.fileName = fileName;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getName() {
|
|
|
+ return "file";
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getOriginalFilename() {
|
|
|
+ return fileName;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getContentType() {
|
|
|
+ return "image/jpeg";
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean isEmpty() {
|
|
|
+ return content == null || content.length == 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public long getSize() {
|
|
|
+ return content.length;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public byte[] getBytes() throws IOException {
|
|
|
+ return content;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public InputStream getInputStream() throws IOException {
|
|
|
+ return new ByteArrayInputStream(content);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void transferTo(java.io.File dest) throws IOException, IllegalStateException {
|
|
|
+ java.nio.file.Files.write(dest.toPath(), content);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|