diff --git a/src/main/java/com/biutag/supervision/controller/price/PriceInformationController.java b/src/main/java/com/biutag/supervision/controller/price/PriceInformationController.java index 42ba59c..89545b1 100644 --- a/src/main/java/com/biutag/supervision/controller/price/PriceInformationController.java +++ b/src/main/java/com/biutag/supervision/controller/price/PriceInformationController.java @@ -13,16 +13,22 @@ import com.biutag.supervision.common.UserContextHolder; import com.biutag.supervision.constants.AppConstants; import com.biutag.supervision.constants.enums.RoleCodeEnum; import com.biutag.supervision.pojo.Result; +import com.biutag.supervision.pojo.entity.price.ExcelRowData; import com.biutag.supervision.pojo.entity.price.PriceFile; import com.biutag.supervision.pojo.entity.price.PriceInformation; import com.biutag.supervision.pojo.model.UserAuth; import com.biutag.supervision.pojo.param.Price.PriceInformationQueryParam; +import com.biutag.supervision.pojo.request.price.ExcelDelRequest; +import com.biutag.supervision.pojo.request.price.ExcelSearchRequest; import com.biutag.supervision.pojo.vo.excel.ExcelPriceInformation; import com.biutag.supervision.pojo.vo.price.PriceInformationVo; import com.biutag.supervision.service.FileService; +import com.biutag.supervision.service.Price.ExcelRowDataService; import com.biutag.supervision.service.Price.PriceFileService; import com.biutag.supervision.service.Price.PriceInformationService; import com.biutag.supervision.service.SupDepartService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -36,20 +42,25 @@ import java.net.URLEncoder; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** + * 价格库信息管理控制器 + * * @author weipeng */ @Slf4j @RequiredArgsConstructor @RestController @RequestMapping("/price") +@Tag(name = "价格库管理", description = "价格库信息导入、搜索、导出等操作") public class PriceInformationController { private final PriceInformationService service; private final FileService fileService; private final PriceFileService priceFileService; private final SupDepartService departService; + private final ExcelRowDataService excelRowDataService; @GetMapping public Result> getPage(PriceInformationQueryParam queryParam){ @@ -134,4 +145,46 @@ public class PriceInformationController { response.setContentType("application/octet-stream"); EasyExcel.write(response.getOutputStream(), ExcelPriceInformation.class).inMemory(Boolean.TRUE).sheet("禁闭台帐").doWrite(list); } + + // ==================== Excel多Sheet导入搜索接口 ==================== + + + @Transactional(rollbackFor = Exception.class) + @PostMapping("/excel/import") + @Operation(summary = "Excel多Sheet导入", description = "导入Excel文件,自动识别所有包含序号列的Sheet并存储数据") + public Result> importExcelMultiSheet(@RequestPart("file") MultipartFile file) throws IOException { log.info("Excel多Sheet导入开始,文件名: {}", file.getOriginalFilename()); + Map result = excelRowDataService.importExcel(file); + return Result.success(result); + } + + + @PostMapping("/excel/search") + @Operation(summary = "Excel数据搜索", description = "根据关键字模糊搜索Excel导入数据") + public Result> searchExcelData(@RequestBody ExcelSearchRequest request) { + log.info("Excel数据搜索,关键字: {}", request.getKeyword()); + Page page = excelRowDataService.search( + request.getKeyword(), + request.getBatchId(), + request.getCurrent(), + request.getSize() + ); + return Result.success(page); + } + + @GetMapping("/excel/batch/list") + @Operation(summary = "批次列表", description = "分页查询所有批次列表,显示文件名、批次名称、上传时间、上传人") + public Result> getBatchListPage( + @RequestParam(required = false) String keyword, + @RequestParam(defaultValue = "1") Integer current, + @RequestParam(defaultValue = "10") Integer size) { + Page page = excelRowDataService.getBatchListPage(keyword, current, size); + return Result.success(page); + } + + @PostMapping("/excel/del") + @Operation(summary = "删除数据", description = "根据批次号删除同一批次的所有数据") + public Result delExcelData(@RequestBody ExcelDelRequest excelDelRequest) { + return excelRowDataService.delExcelData(excelDelRequest); + } + } diff --git a/src/main/java/com/biutag/supervision/mapper/ExcelRowDataMapper.java b/src/main/java/com/biutag/supervision/mapper/ExcelRowDataMapper.java new file mode 100644 index 0000000..ab7c5dc --- /dev/null +++ b/src/main/java/com/biutag/supervision/mapper/ExcelRowDataMapper.java @@ -0,0 +1,38 @@ +package com.biutag.supervision.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.biutag.supervision.pojo.entity.price.ExcelRowData; +import org.apache.ibatis.annotations.Param; + +/** + * Excel导入数据Mapper + * + * @author system + */ +public interface ExcelRowDataMapper extends BaseMapper { + + /** + * 分页模糊搜索Excel行数据 + * 遍历row_data JSON中的所有value进行关键字匹配 + * + * @param page 分页对象 + * @param keyword 搜索关键字 + * @param batchId 批次ID(可选) + * @return 分页结果 + */ + Page searchPage(@Param("page") Page page, + @Param("keyword") String keyword, + @Param("batchId") String batchId); + + /** + * 分页查询批次列表(按批次分组) + * 返回每个批次的汇总信息:文件名、批次名称、上传时间、上传人等 + * + * @param page 分页对象 + * @param keyword 搜索关键字(可选,搜索文件名或批次名称) + * @return 分页结果 + */ + Page selectBatchListPage(@Param("page") Page page, + @Param("keyword") String keyword); +} diff --git a/src/main/java/com/biutag/supervision/pojo/entity/price/ExcelRowData.java b/src/main/java/com/biutag/supervision/pojo/entity/price/ExcelRowData.java new file mode 100644 index 0000000..d7d7b27 --- /dev/null +++ b/src/main/java/com/biutag/supervision/pojo/entity/price/ExcelRowData.java @@ -0,0 +1,67 @@ +package com.biutag.supervision.pojo.entity.price; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * Excel多Sheet导入数据实体 + * 用于存储从Excel导入的每行数据,支持多Sheet导入和模糊搜索 + * + * @author system + */ +@Data +@TableName("excel_row_data") +@Schema(description = "Excel导入行数据") +public class ExcelRowData { + + @TableId(type = IdType.AUTO) + @Schema(description = "主键ID") + private Long id; + + @TableField("file_name") + @Schema(description = "原文件名") + private String fileName; + + @TableField("file_path") + @Schema(description = "文件保存路径") + private String filePath; + + @TableField("sheet_name") + @Schema(description = "Sheet名称") + private String sheetName; + + @TableField("row_index") + @Schema(description = "Excel行号(从2开始)") + private Integer rowIndex; + + @TableField("row_data") + @Schema(description = "整行数据JSON,格式:{\"序号\":\"1\",\"名称\":\"aaa\",\"价格\":\"333\"}") + private String rowData; + + @TableField("batch_id") + @Schema(description = "批次号UUID,用于关联同一批次导入的数据") + private String batchId; + + @TableField("batch_name") + @Schema(description = "批次名称(用户自定义)") + private String batchName; + + @TableField("upload_time") + @Schema(description = "上传时间") + private LocalDateTime uploadTime; + + @TableField("upload_user") + @Schema(description = "上传人") + private String uploadUser; + + + @TableField("upload_user_name") + @Schema(description = "上传人名字") + private String uploadUserName; +} diff --git a/src/main/java/com/biutag/supervision/pojo/request/price/ExcelDelRequest.java b/src/main/java/com/biutag/supervision/pojo/request/price/ExcelDelRequest.java new file mode 100644 index 0000000..366e9c4 --- /dev/null +++ b/src/main/java/com/biutag/supervision/pojo/request/price/ExcelDelRequest.java @@ -0,0 +1,27 @@ +package com.biutag.supervision.pojo.request.price; + +import com.biutag.supervision.aop.ParamChecked; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +/** + * Excel数据搜索请求参数 + * + * @author system + */ +@Getter +@Setter +@Schema(description = "删除参数") +public class ExcelDelRequest implements ParamChecked { + + @Schema(description = "批次") + private String batchName; + + @Override + public void check() { + if (batchName == null) { + throw new IllegalArgumentException("批次不能为空"); + } + } +} diff --git a/src/main/java/com/biutag/supervision/pojo/request/price/ExcelSearchRequest.java b/src/main/java/com/biutag/supervision/pojo/request/price/ExcelSearchRequest.java new file mode 100644 index 0000000..1ed0b29 --- /dev/null +++ b/src/main/java/com/biutag/supervision/pojo/request/price/ExcelSearchRequest.java @@ -0,0 +1,28 @@ +package com.biutag.supervision.pojo.request.price; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +/** + * Excel数据搜索请求参数 + * + * @author system + */ +@Getter +@Setter +@Schema(description = "Excel数据搜索请求参数") +public class ExcelSearchRequest { + + @Schema(description = "搜索关键字,会匹配row_data中的所有值") + private String keyword; + + @Schema(description = "当前页码(从1开始)") + private Integer current = 1; + + @Schema(description = "每页大小") + private Integer size = 10; + + @Schema(description = "批次ID(可选,用于查询指定批次的数据)") + private String batchId; +} diff --git a/src/main/java/com/biutag/supervision/service/Price/ExcelRowDataService.java b/src/main/java/com/biutag/supervision/service/Price/ExcelRowDataService.java new file mode 100644 index 0000000..28719b4 --- /dev/null +++ b/src/main/java/com/biutag/supervision/service/Price/ExcelRowDataService.java @@ -0,0 +1,238 @@ +package com.biutag.supervision.service.Price; + +import cn.hutool.core.io.FileUtil; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelReader; +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.read.listener.ReadListener; +import com.alibaba.excel.read.metadata.ReadSheet; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.biutag.supervision.common.UserContextHolder; +import com.biutag.supervision.mapper.ExcelRowDataMapper; +import com.biutag.supervision.pojo.Result; +import com.biutag.supervision.pojo.entity.price.ExcelRowData; +import com.biutag.supervision.pojo.entity.price.PriceFile; +import com.biutag.supervision.pojo.model.UserAuth; +import com.biutag.supervision.pojo.request.price.ExcelDelRequest; +import com.biutag.supervision.service.FileService; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +@Slf4j +@RequiredArgsConstructor +@Service +public class ExcelRowDataService extends ServiceImpl { + + + private final FileService fileService; + + @Transactional(rollbackFor = Exception.class) + public Map importExcel(MultipartFile file) throws IOException { + log.info("文件导入中------------------------------"); + UserAuth user = UserContextHolder.getCurrentUser(); + String filePath = fileService.upload(file); + String fileNameType = FileUtil.extName(file.getOriginalFilename()); + if (!"xls".equals(fileNameType) && !"xlsx".equals(fileNameType)) { + throw new RuntimeException("仅支持 xls/xlsx 格式文件的导入"); + } + + // 批次信息 + String fileName = file.getOriginalFilename(); + LocalDateTime uploadTime = LocalDateTime.now(); + String batchId = uploadTime.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + String batchName = fileName + batchId; + + log.info("开始解析表格文件: {}, 批次ID: {}", fileName, batchId); + + List dataList = new ArrayList<>(); + int totalRows = 0; + + // 获取Excel中所有Sheet信息 + ExcelReader excelReader = EasyExcel.read(file.getInputStream()).build(); + List sheets = excelReader.excelExecutor().sheetList(); + excelReader.close(); + + // 用于存储每个Sheet的表头和数据的临时对象 + // 修改:表头结构改为 表头名称 -> 列索引列表(支持合并单元格) + Map>> sheetDataMap = new LinkedHashMap<>(); + Map>> sheetHeaderMap = new LinkedHashMap<>(); + + // 遍历每个Sheet并读取数据 + for (ReadSheet sheet : sheets) { + try { + InputStream inputStream = file.getInputStream(); + final int[] sequenceRowIndex = {-1}; + // 修改:表头Map结构:表头名称 -> 列索引列表 + final Map> headerMap = new LinkedHashMap<>(); + final List> allData = new ArrayList<>(); + + ExcelReader reader = EasyExcel.read(inputStream, new ReadListener>() { + @Override + public void invoke(Map data, AnalysisContext context) { + int currentRowIndex = context.readRowHolder().getRowIndex(); + // 动态查找"序号"列所在行 + if (sequenceRowIndex[0] == -1) { + boolean hasSequenceColumn = data.values().stream() + .anyMatch(v -> v != null && "序号".equals(v.toString().trim())); + if (hasSequenceColumn) { + sequenceRowIndex[0] = currentRowIndex; + + // ========== 修改:处理合并单元格逻辑 ========== + String lastHeaderName = null; + for (Map.Entry entry : data.entrySet()) { + Integer colIndex = entry.getKey(); + Object cellValue = entry.getValue(); + + if (cellValue != null && !cellValue.toString().trim().isEmpty()) { + // 有值的列,作为新的表头 + lastHeaderName = cellValue.toString().trim(); + headerMap.computeIfAbsent(lastHeaderName, k -> new ArrayList<>()).add(colIndex); + } else if (lastHeaderName != null) { + // 合并单元格(当前列为空),沿用上一个表头名称 + headerMap.get(lastHeaderName).add(colIndex); + } + } + } + } + allData.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + } + }).build(); // ✅ 修正:括号位置正确 + + reader.read(EasyExcel.readSheet(sheet.getSheetNo()).build()); + reader.close(); + + // 只有找到"序号"列的Sheet才处理 + if (sequenceRowIndex[0] >= 0) { + sheetHeaderMap.put(sheet.getSheetName(), headerMap); + sheetDataMap.put(sheet.getSheetName(), allData); + } + } catch (Exception e) { + log.warn("读取Sheet异常: {}, 错误: {}", sheet.getSheetName(), e.getMessage()); + } + } + + // ObjectMapper提到循环外部 + ObjectMapper objectMapper = new ObjectMapper(); + + // 处理每个包含"序号"列的Sheet + for (Map.Entry>> entry : sheetDataMap.entrySet()) { + String sheetName = entry.getKey(); + List> allData = entry.getValue(); + // 修改:类型匹配新的表头结构 + Map> headerMap = sheetHeaderMap.get(sheetName); + + // 查找"序号"所在行的索引 + int sequenceRowIndex = -1; + for (int i = 0; i < allData.size(); i++) { + Map row = allData.get(i); + if (row != null && row.values().stream().anyMatch(v -> v != null && "序号".equals(v.toString().trim()))) { + sequenceRowIndex = i; + break; + } + } + + // 从"序号"行的下一行开始处理数据 + for (int i = sequenceRowIndex + 1; i < allData.size(); i++) { + Map row = allData.get(i); + if (row == null || row.isEmpty()) continue; + + // ========== 修改:构建Map,处理合并单元格(多列对应同一表头) ========== + Map rowMap = new LinkedHashMap<>(); + for (Map.Entry> hEntry : headerMap.entrySet()) { + String headerName = hEntry.getKey(); + List colIndices = hEntry.getValue(); + + List values = new ArrayList<>(); + for (Integer colIndex : colIndices) { + Object value = row.get(colIndex); + values.add(value != null ? value : ""); + } + + // 只有一个值直接存储,多个值用数组 + if (values.size() == 1) { + rowMap.put(headerName, values.get(0)); + } else { + rowMap.put(headerName, values); + } + } + + // 序列化为 JSON 字符串 + String rowDataJson = objectMapper.writeValueAsString(rowMap); + + ExcelRowData rowData = new ExcelRowData(); + rowData.setFileName(fileName); + rowData.setSheetName(sheetName); + rowData.setRowIndex(i + 1); + rowData.setRowData(rowDataJson); + rowData.setBatchId(batchId); + rowData.setBatchName(batchName); + rowData.setUploadTime(uploadTime); + rowData.setUploadUser(user.getUserName()); + rowData.setUploadUserName(user.getNickName()); + rowData.setFilePath(filePath); + dataList.add(rowData); + totalRows++; + } + } + + // 批量保存 + if (!dataList.isEmpty()) { + saveBatch(dataList); + } + + log.info("Excel导入完成,共导入 {} 行数据", totalRows); + + // 返回结果 + Map result = new HashMap<>(); + result.put("batchId", batchId); + result.put("batchName", batchName); + result.put("fileName", fileName); + result.put("totalRows", totalRows); + result.put("uploadTime", uploadTime); + + return result; + } + + public Page search(String keyword, String batchId, Integer current, Integer size) { + Page page = new Page<>(current, size); + return baseMapper.searchPage(page, keyword, batchId); + } + + /** + * 分页查询批次列表 + * 返回每个批次的汇总信息 + * + * @param keyword 搜索关键字(可选) + * @param current 当前页 + * @param size 每页大小 + * @return 分页结果 + */ + public Page getBatchListPage(String keyword, Integer current, Integer size) { + Page page = new Page<>(current, size); + return baseMapper.selectBatchListPage(page, keyword); + } + + public Result delExcelData(ExcelDelRequest excelDelRequest) { + LambdaQueryWrapper excelRowDataLambdaQueryWrapper = new LambdaQueryWrapper<>(); + excelRowDataLambdaQueryWrapper.eq(ExcelRowData::getBatchName, excelDelRequest.getBatchName()); + int delete = baseMapper.delete(excelRowDataLambdaQueryWrapper); + return Result.success(Boolean.TRUE); + } +} \ No newline at end of file diff --git a/src/main/resources/mapper/ExcelRowDataMapper.xml b/src/main/resources/mapper/ExcelRowDataMapper.xml new file mode 100644 index 0000000..556fd79 --- /dev/null +++ b/src/main/resources/mapper/ExcelRowDataMapper.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + diff --git a/src/main/resources/sql/excel_row_data.sql b/src/main/resources/sql/excel_row_data.sql new file mode 100644 index 0000000..a6eb07b --- /dev/null +++ b/src/main/resources/sql/excel_row_data.sql @@ -0,0 +1,14 @@ +-- Excel多Sheet导入数据表 +CREATE TABLE `excel_row_data` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `file_name` VARCHAR(255) NOT NULL COMMENT '原文件名', + `sheet_name` VARCHAR(255) NOT NULL COMMENT 'Sheet名称', + `row_index` INT NOT NULL COMMENT 'Excel行号(从2开始)', + `row_data` JSON NOT NULL COMMENT '整行数据JSON', + `batch_id` VARCHAR(64) NOT NULL COMMENT '批次号UUID', + `batch_name` VARCHAR(255) COMMENT '批次名称', + `upload_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间', + PRIMARY KEY (`id`), + INDEX `idx_batch_id` (`batch_id`), + INDEX `idx_upload_time` (`upload_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;