Browse Source

feat:价格信息库初步完成

master
buaixuexideshitongxue 3 weeks ago
parent
commit
4cf5652358
  1. 53
      src/main/java/com/biutag/supervision/controller/price/PriceInformationController.java
  2. 38
      src/main/java/com/biutag/supervision/mapper/ExcelRowDataMapper.java
  3. 67
      src/main/java/com/biutag/supervision/pojo/entity/price/ExcelRowData.java
  4. 27
      src/main/java/com/biutag/supervision/pojo/request/price/ExcelDelRequest.java
  5. 28
      src/main/java/com/biutag/supervision/pojo/request/price/ExcelSearchRequest.java
  6. 238
      src/main/java/com/biutag/supervision/service/Price/ExcelRowDataService.java
  7. 57
      src/main/resources/mapper/ExcelRowDataMapper.xml
  8. 14
      src/main/resources/sql/excel_row_data.sql

53
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<Page<PriceInformationVo>> 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<Map<String, Object>> importExcelMultiSheet(@RequestPart("file") MultipartFile file) throws IOException { log.info("Excel多Sheet导入开始,文件名: {}", file.getOriginalFilename());
Map<String, Object> result = excelRowDataService.importExcel(file);
return Result.success(result);
}
@PostMapping("/excel/search")
@Operation(summary = "Excel数据搜索", description = "根据关键字模糊搜索Excel导入数据")
public Result<Page<ExcelRowData>> searchExcelData(@RequestBody ExcelSearchRequest request) {
log.info("Excel数据搜索,关键字: {}", request.getKeyword());
Page<ExcelRowData> page = excelRowDataService.search(
request.getKeyword(),
request.getBatchId(),
request.getCurrent(),
request.getSize()
);
return Result.success(page);
}
@GetMapping("/excel/batch/list")
@Operation(summary = "批次列表", description = "分页查询所有批次列表,显示文件名、批次名称、上传时间、上传人")
public Result<Page<ExcelRowData>> getBatchListPage(
@RequestParam(required = false) String keyword,
@RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "10") Integer size) {
Page<ExcelRowData> page = excelRowDataService.getBatchListPage(keyword, current, size);
return Result.success(page);
}
@PostMapping("/excel/del")
@Operation(summary = "删除数据", description = "根据批次号删除同一批次的所有数据")
public Result<Boolean> delExcelData(@RequestBody ExcelDelRequest excelDelRequest) {
return excelRowDataService.delExcelData(excelDelRequest);
}
}

38
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<ExcelRowData> {
/**
* 分页模糊搜索Excel行数据
* 遍历row_data JSON中的所有value进行关键字匹配
*
* @param page 分页对象
* @param keyword 搜索关键字
* @param batchId 批次ID可选
* @return 分页结果
*/
Page<ExcelRowData> searchPage(@Param("page") Page<ExcelRowData> page,
@Param("keyword") String keyword,
@Param("batchId") String batchId);
/**
* 分页查询批次列表按批次分组
* 返回每个批次的汇总信息文件名批次名称上传时间上传人等
*
* @param page 分页对象
* @param keyword 搜索关键字可选搜索文件名或批次名称
* @return 分页结果
*/
Page<ExcelRowData> selectBatchListPage(@Param("page") Page<ExcelRowData> page,
@Param("keyword") String keyword);
}

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

27
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("批次不能为空");
}
}
}

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

238
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<ExcelRowDataMapper, ExcelRowData> {
private final FileService fileService;
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> 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<ExcelRowData> dataList = new ArrayList<>();
int totalRows = 0;
// 获取Excel中所有Sheet信息
ExcelReader excelReader = EasyExcel.read(file.getInputStream()).build();
List<ReadSheet> sheets = excelReader.excelExecutor().sheetList();
excelReader.close();
// 用于存储每个Sheet的表头和数据的临时对象
// 修改:表头结构改为 表头名称 -> 列索引列表(支持合并单元格)
Map<String, List<Map<Integer, Object>>> sheetDataMap = new LinkedHashMap<>();
Map<String, Map<String, List<Integer>>> sheetHeaderMap = new LinkedHashMap<>();
// 遍历每个Sheet并读取数据
for (ReadSheet sheet : sheets) {
try {
InputStream inputStream = file.getInputStream();
final int[] sequenceRowIndex = {-1};
// 修改:表头Map结构:表头名称 -> 列索引列表
final Map<String, List<Integer>> headerMap = new LinkedHashMap<>();
final List<Map<Integer, Object>> allData = new ArrayList<>();
ExcelReader reader = EasyExcel.read(inputStream, new ReadListener<Map<Integer, Object>>() {
@Override
public void invoke(Map<Integer, Object> 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<Integer, Object> 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<String, List<Map<Integer, Object>>> entry : sheetDataMap.entrySet()) {
String sheetName = entry.getKey();
List<Map<Integer, Object>> allData = entry.getValue();
// 修改:类型匹配新的表头结构
Map<String, List<Integer>> headerMap = sheetHeaderMap.get(sheetName);
// 查找"序号"所在行的索引
int sequenceRowIndex = -1;
for (int i = 0; i < allData.size(); i++) {
Map<Integer, Object> 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<Integer, Object> row = allData.get(i);
if (row == null || row.isEmpty()) continue;
// ========== 修改:构建Map,处理合并单元格(多列对应同一表头) ==========
Map<String, Object> rowMap = new LinkedHashMap<>();
for (Map.Entry<String, List<Integer>> hEntry : headerMap.entrySet()) {
String headerName = hEntry.getKey();
List<Integer> colIndices = hEntry.getValue();
List<Object> 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<String, Object> 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<ExcelRowData> search(String keyword, String batchId, Integer current, Integer size) {
Page<ExcelRowData> page = new Page<>(current, size);
return baseMapper.searchPage(page, keyword, batchId);
}
/**
* 分页查询批次列表
* 返回每个批次的汇总信息
*
* @param keyword 搜索关键字可选
* @param current 当前页
* @param size 每页大小
* @return 分页结果
*/
public Page<ExcelRowData> getBatchListPage(String keyword, Integer current, Integer size) {
Page<ExcelRowData> page = new Page<>(current, size);
return baseMapper.selectBatchListPage(page, keyword);
}
public Result<Boolean> delExcelData(ExcelDelRequest excelDelRequest) {
LambdaQueryWrapper<ExcelRowData> excelRowDataLambdaQueryWrapper = new LambdaQueryWrapper<>();
excelRowDataLambdaQueryWrapper.eq(ExcelRowData::getBatchName, excelDelRequest.getBatchName());
int delete = baseMapper.delete(excelRowDataLambdaQueryWrapper);
return Result.success(Boolean.TRUE);
}
}

57
src/main/resources/mapper/ExcelRowDataMapper.xml

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.biutag.supervision.mapper.ExcelRowDataMapper">
<!-- 分页模糊搜索Excel行数据 -->
<select id="searchPage" resultType="com.biutag.supervision.pojo.entity.price.ExcelRowData">
SELECT
id,
file_name AS fileName,
file_path as filePath,
sheet_name AS sheetName,
row_index AS rowIndex,
row_data AS rowData,
batch_id AS batchId,
batch_name AS batchName,
upload_time AS uploadTime
FROM excel_row_data
WHERE 1=1
<if test="keyword != null and keyword != ''">
AND row_data LIKE CONCAT('%', #{keyword}, '%')
</if>
<if test="batchId != null and batchId != ''">
AND batch_id = #{batchId}
</if>
ORDER BY upload_time DESC, id ASC
</select>
<!-- 分页查询批次列表(按批次分组) -->
<select id="selectBatchListPage" resultType="com.biutag.supervision.pojo.entity.price.ExcelRowData">
SELECT
erd.id,
erd.file_name AS fileName,
erd.sheet_name AS sheetName,
erd.batch_id AS batchId,
erd.batch_name AS batchName,
erd.upload_time AS uploadTime,
erd.upload_user AS uploadUser,
erd.upload_user_name AS uploadUserName,
erd.file_path AS filePath,
(SELECT COUNT(*) FROM excel_row_data WHERE batch_id = erd.batch_id) AS rowIndex
FROM (
SELECT batch_id, MAX(id) AS max_id
FROM excel_row_data
GROUP BY batch_id
) AS latest
INNER JOIN excel_row_data erd ON erd.id = latest.max_id
WHERE 1=1
<if test="keyword != null and keyword != ''">
AND (erd.file_name LIKE CONCAT('%', #{keyword}, '%')
OR erd.batch_name LIKE CONCAT('%', #{keyword}, '%'))
</if>
ORDER BY erd.upload_time DESC
</select>
</mapper>

14
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;
Loading…
Cancel
Save