|
|
@@ -0,0 +1,430 @@
|
|
|
+package shop.alien.storeplatform.service.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.collection.CollectionUtil;
|
|
|
+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.XSSFWorkbook;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.web.multipart.MultipartFile;
|
|
|
+import shop.alien.entity.result.R;
|
|
|
+import shop.alien.storeplatform.entity.StorePlatformSportsEquipmentFacility;
|
|
|
+import shop.alien.storeplatform.mapper.StorePlatformSportsEquipmentFacilityMapper;
|
|
|
+import shop.alien.storeplatform.service.StorePlatformSportsEquipmentFacilityService;
|
|
|
+import shop.alien.storeplatform.vo.StorePlatformSportsEquipmentFacilityImportVo;
|
|
|
+
|
|
|
+import java.io.InputStream;
|
|
|
+import java.lang.reflect.Field;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 商户平台-运动器材设施服务实现类
|
|
|
+ *
|
|
|
+ * @author system
|
|
|
+ * @since 2025-01-XX
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class StorePlatformSportsEquipmentFacilityServiceImpl extends ServiceImpl<StorePlatformSportsEquipmentFacilityMapper, StorePlatformSportsEquipmentFacility> implements StorePlatformSportsEquipmentFacilityService {
|
|
|
+
|
|
|
+ private final StorePlatformSportsEquipmentFacilityMapper facilityMapper;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public StorePlatformSportsEquipmentFacility getById(Integer id) {
|
|
|
+ log.info("StorePlatformSportsEquipmentFacilityServiceImpl.getById id={}", id);
|
|
|
+ if (id == null) {
|
|
|
+ throw new RuntimeException("设施ID不能为空");
|
|
|
+ }
|
|
|
+ return super.getById(id);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<StorePlatformSportsEquipmentFacility> getListByStoreId(Integer storeId) {
|
|
|
+ log.info("StorePlatformSportsEquipmentFacilityServiceImpl.getListByStoreId storeId={}", storeId);
|
|
|
+ if (storeId == null) {
|
|
|
+ throw new RuntimeException("门店ID不能为空");
|
|
|
+ }
|
|
|
+ LambdaQueryWrapper<StorePlatformSportsEquipmentFacility> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(StorePlatformSportsEquipmentFacility::getStoreId, storeId)
|
|
|
+ .eq(StorePlatformSportsEquipmentFacility::getDeleteFlag, 0)
|
|
|
+ .orderByAsc(StorePlatformSportsEquipmentFacility::getFacilityCategory)
|
|
|
+ .orderByAsc(StorePlatformSportsEquipmentFacility::getId);
|
|
|
+ return this.list(queryWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<StorePlatformSportsEquipmentFacility> getListByStoreIdAndCategory(Integer storeId, Integer facilityCategory) {
|
|
|
+ log.info("StorePlatformSportsEquipmentFacilityServiceImpl.getListByStoreIdAndCategory storeId={}, facilityCategory={}", storeId, facilityCategory);
|
|
|
+ if (storeId == null) {
|
|
|
+ throw new RuntimeException("门店ID不能为空");
|
|
|
+ }
|
|
|
+ LambdaQueryWrapper<StorePlatformSportsEquipmentFacility> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(StorePlatformSportsEquipmentFacility::getStoreId, storeId)
|
|
|
+ .eq(StorePlatformSportsEquipmentFacility::getDeleteFlag, 0);
|
|
|
+ if (facilityCategory != null) {
|
|
|
+ queryWrapper.eq(StorePlatformSportsEquipmentFacility::getFacilityCategory, facilityCategory);
|
|
|
+ }
|
|
|
+ queryWrapper.orderByAsc(StorePlatformSportsEquipmentFacility::getFacilityCategory)
|
|
|
+ .orderByAsc(StorePlatformSportsEquipmentFacility::getId);
|
|
|
+ return this.list(queryWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<String> saveFacility(StorePlatformSportsEquipmentFacility facility) {
|
|
|
+ log.info("StorePlatformSportsEquipmentFacilityServiceImpl.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.getQuantity() == null || facility.getQuantity() <= 0) {
|
|
|
+ return R.fail("数量必须大于0");
|
|
|
+ }
|
|
|
+
|
|
|
+ boolean flag = this.save(facility);
|
|
|
+ if (!flag) {
|
|
|
+ return R.fail("新增设施失败");
|
|
|
+ }
|
|
|
+ return R.success("新增设施成功");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<String> updateFacility(StorePlatformSportsEquipmentFacility facility) {
|
|
|
+ log.info("StorePlatformSportsEquipmentFacilityServiceImpl.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.getQuantity() == null || facility.getQuantity() <= 0) {
|
|
|
+ return R.fail("数量必须大于0");
|
|
|
+ }
|
|
|
+
|
|
|
+ boolean flag = this.updateById(facility);
|
|
|
+ if (!flag) {
|
|
|
+ return R.fail("修改设施失败");
|
|
|
+ }
|
|
|
+ return R.success("修改设施成功");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<String> saveOrUpdateFacility(StorePlatformSportsEquipmentFacility facility) {
|
|
|
+ log.info("StorePlatformSportsEquipmentFacilityServiceImpl.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("StorePlatformSportsEquipmentFacilityServiceImpl.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("StorePlatformSportsEquipmentFacilityServiceImpl.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("StorePlatformSportsEquipmentFacilityServiceImpl.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 = StorePlatformSportsEquipmentFacilityImportVo.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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 读取数据行(从第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++;
|
|
|
+ StorePlatformSportsEquipmentFacilityImportVo excelVo = new StorePlatformSportsEquipmentFacilityImportVo();
|
|
|
+
|
|
|
+ // 读取每个字段
|
|
|
+ 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) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 处理普通字段
|
|
|
+ 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("quantity", "数量");
|
|
|
+ fieldMapping.put("brand", "品牌");
|
|
|
+ fieldMapping.put("description", "描述");
|
|
|
+ fieldMapping.put("displayInStoreDetail", "展示在店铺详情(展示/隐藏)");
|
|
|
+
|
|
|
+ String expectedHeader = fieldMapping.get(fieldName);
|
|
|
+ return expectedHeader != null && expectedHeader.equals(headerName);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置字段值
|
|
|
+ */
|
|
|
+ private void setFieldValue(StorePlatformSportsEquipmentFacilityImportVo 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
|
|
|
+ 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 {
|
|
|
+ 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(StorePlatformSportsEquipmentFacilityImportVo excelVo, Integer storeId, int rowNum) {
|
|
|
+ // 校验必填字段
|
|
|
+ if (StringUtils.isEmpty(excelVo.getFacilityName())) {
|
|
|
+ throw new RuntimeException("设施名称不能为空");
|
|
|
+ }
|
|
|
+ if (excelVo.getFacilityCategory() == null) {
|
|
|
+ throw new RuntimeException("设施分类不能为空");
|
|
|
+ }
|
|
|
+ if (excelVo.getQuantity() == null || excelVo.getQuantity() <= 0) {
|
|
|
+ throw new RuntimeException("数量必须大于0");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建StorePlatformSportsEquipmentFacility对象
|
|
|
+ StorePlatformSportsEquipmentFacility facility = new StorePlatformSportsEquipmentFacility();
|
|
|
+ facility.setStoreId(storeId);
|
|
|
+ facility.setFacilityCategory(excelVo.getFacilityCategory());
|
|
|
+ facility.setFacilityName(excelVo.getFacilityName());
|
|
|
+ facility.setQuantity(excelVo.getQuantity());
|
|
|
+ facility.setBrand(excelVo.getBrand());
|
|
|
+ facility.setDescription(excelVo.getDescription());
|
|
|
+ facility.setDisplayInStoreDetail(excelVo.getDisplayInStoreDetail() != null ? excelVo.getDisplayInStoreDetail() : 0);
|
|
|
+
|
|
|
+ // 保存设施
|
|
|
+ boolean flag = this.save(facility);
|
|
|
+ if (!flag) {
|
|
|
+ throw new RuntimeException("保存设施失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取单元格值(字符串格式)
|
|
|
+ */
|
|
|
+ private String getCellValueAsString(Cell cell) {
|
|
|
+ if (cell == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (cell.getCellType()) {
|
|
|
+ case STRING:
|
|
|
+ return cell.getStringCellValue();
|
|
|
+ case NUMERIC:
|
|
|
+ if (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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|