feat(ems): 添加备件库记录盘点功能
- 新增盘点记录相关API和UI - 实现备件库记录的动态列展示 - 添加盘点记录的添加和删除功能 - 优化表单布局和数据处理逻辑boardTest
parent
ff83ba78ec
commit
7d4811a57f
@ -0,0 +1,280 @@
|
||||
# Excel图片导入导出使用指南
|
||||
|
||||
## 技术方案概述
|
||||
|
||||
### 技术架构
|
||||
- **框架**: RuoYi (SpringBoot + Vue + Redis + MyBatis)
|
||||
- **部署环境**: 麒麟系统
|
||||
- **数据库**: TiDB
|
||||
- **Excel处理**: Apache POI 4.1.2
|
||||
- **图片存储**: 本地文件系统
|
||||
|
||||
### 核心功能
|
||||
1. Excel中嵌入图片的导出
|
||||
2. Excel中图片的提取和导入
|
||||
3. 图片文件的本地存储管理
|
||||
4. 数据库路径信息的维护
|
||||
|
||||
## 实现架构
|
||||
|
||||
### 1. 核心工具类
|
||||
|
||||
#### ImageExcelUtils.java
|
||||
专门处理Excel图片操作的工具类
|
||||
|
||||
**主要功能:**
|
||||
- `insertImageToCell()`: 向Excel单元格插入图片
|
||||
- `extractImagesFromSheet()`: 从Excel提取所有图片
|
||||
- `saveImageFromExcel()`: 保存提取的图片到本地
|
||||
- `isSupportedImageFormat()`: 验证图片格式
|
||||
- `cleanupTempImages()`: 清理临时图片文件
|
||||
|
||||
**支持格式:**
|
||||
- JPG/JPEG
|
||||
- PNG
|
||||
- GIF (转换为JPEG)
|
||||
- BMP (转换为JPEG)
|
||||
|
||||
#### ExcelUtil.java (增强版)
|
||||
集成图片处理功能的Excel工具类
|
||||
|
||||
**新增功能:**
|
||||
- 在`setCellVo()`中集成图片插入
|
||||
- 在`importExcel()`中集成图片提取
|
||||
- 自动处理图片的导入导出流程
|
||||
|
||||
### 2. 实体类配置
|
||||
|
||||
```java
|
||||
public class PowerEnergySparePartsRegistration {
|
||||
/** 图片位置 */
|
||||
@Excel(name = "图片位置",
|
||||
cellType = Excel.ColumnType.IMAGE,
|
||||
width = 30,
|
||||
height = 80)
|
||||
private String imageLocation;
|
||||
}
|
||||
```
|
||||
|
||||
**注解说明:**
|
||||
- `cellType = Excel.ColumnType.IMAGE`: 指定为图片类型
|
||||
- `width = 30`: 单元格宽度
|
||||
- `height = 80`: 单元格高度
|
||||
|
||||
### 3. Controller层实现
|
||||
|
||||
#### 新增接口
|
||||
|
||||
1. **导入模板下载**
|
||||
```
|
||||
POST /ems/info/powerEnergyRegistration/importTemplate
|
||||
```
|
||||
|
||||
2. **数据导入(含图片)**
|
||||
```
|
||||
POST /ems/info/powerEnergyRegistration/importData
|
||||
参数: file (MultipartFile), updateSupport (boolean)
|
||||
```
|
||||
|
||||
3. **数据导出(含图片)**
|
||||
```
|
||||
POST /ems/info/powerEnergyRegistration/export
|
||||
```
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 导出流程
|
||||
|
||||
1. **准备数据**
|
||||
```java
|
||||
List<PowerEnergySparePartsRegistration> list = service.selectList(params);
|
||||
```
|
||||
|
||||
2. **设置图片路径**
|
||||
确保实体对象的`imageLocation`字段包含正确的图片路径
|
||||
|
||||
3. **执行导出**
|
||||
```java
|
||||
ExcelUtil<PowerEnergySparePartsRegistration> util = new ExcelUtil<>(PowerEnergySparePartsRegistration.class);
|
||||
util.exportExcel(response, list, "备件登记数据");
|
||||
```
|
||||
|
||||
4. **结果**
|
||||
- 生成包含图片的Excel文件
|
||||
- 图片自动嵌入到对应单元格中
|
||||
- 图片尺寸根据注解自动调整
|
||||
|
||||
### 导入流程
|
||||
|
||||
1. **上传Excel文件**
|
||||
- 支持.xlsx和.xls格式
|
||||
- 文件中可包含嵌入的图片
|
||||
|
||||
2. **图片提取**
|
||||
```java
|
||||
List<PowerEnergySparePartsRegistration> list = util.importExcel(inputStream);
|
||||
```
|
||||
|
||||
3. **图片处理**
|
||||
- 自动提取Excel中的图片
|
||||
- 保存到服务器本地路径: `/profile/excel-images/`
|
||||
- 生成唯一文件名避免冲突
|
||||
|
||||
4. **数据验证**
|
||||
- 验证必填字段
|
||||
- 验证图片格式
|
||||
- 检查重复记录
|
||||
|
||||
5. **数据保存**
|
||||
- 图片路径保存到数据库
|
||||
- 支持新增和更新模式
|
||||
|
||||
## 图片存储结构
|
||||
|
||||
### 存储路径
|
||||
```
|
||||
{ruoyi.profile}/excel-images/
|
||||
├── 2025/01/28/
|
||||
│ ├── uuid1.jpg
|
||||
│ ├── uuid2.png
|
||||
│ └── ...
|
||||
└── ...
|
||||
```
|
||||
|
||||
### 路径格式
|
||||
- **完整路径**: `/media/tao_iot/ruoyi/uploadPath/excel-images/2025/01/28/uuid.jpg`
|
||||
- **数据库存储**: `/excel-images/2025/01/28/uuid.jpg`
|
||||
- **访问URL**: `http://domain/profile/excel-images/2025/01/28/uuid.jpg`
|
||||
|
||||
## 配置要求
|
||||
|
||||
### 1. 系统配置 (application.yml)
|
||||
```yaml
|
||||
ruoyi:
|
||||
profile: /media/tao_iot/ruoyi/uploadPath
|
||||
```
|
||||
|
||||
### 2. 权限配置
|
||||
```
|
||||
ems/info:powerEnergyRegistration:import
|
||||
ems/info:powerEnergyRegistration:export
|
||||
ems/info:powerEnergyRegistration:list
|
||||
```
|
||||
|
||||
### 3. 文件系统要求
|
||||
- 确保上传目录有读写权限
|
||||
- 预留足够磁盘空间
|
||||
- 定期清理临时文件
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 常见错误及解决方案
|
||||
|
||||
1. **图片格式不支持**
|
||||
- 错误: `图片格式不支持: xxx.tiff`
|
||||
- 解决: 使用支持的格式 (jpg/png/gif/bmp)
|
||||
|
||||
2. **文件路径不存在**
|
||||
- 错误: `图片文件不存在: /path/to/image.jpg`
|
||||
- 解决: 检查图片路径是否正确
|
||||
|
||||
3. **权限不足**
|
||||
- 错误: `创建图片存储目录失败`
|
||||
- 解决: 检查文件系统权限
|
||||
|
||||
4. **内存不足**
|
||||
- 错误: `OutOfMemoryError`
|
||||
- 解决: 增加JVM内存或分批处理
|
||||
|
||||
### 异常处理机制
|
||||
|
||||
1. **导入失败时自动清理临时图片**
|
||||
2. **详细的错误信息反馈**
|
||||
3. **事务回滚保证数据一致性**
|
||||
4. **日志记录便于问题排查**
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
### 1. 图片处理优化
|
||||
- 控制图片尺寸 (建议 < 1MB)
|
||||
- 批量处理时分页处理
|
||||
- 使用图片压缩减少存储空间
|
||||
|
||||
### 2. Excel处理优化
|
||||
- 大数据量时使用SXSSFWorkbook流式处理
|
||||
- 设置合适的缓存行数 (默认500)
|
||||
- 及时释放资源避免内存泄漏
|
||||
|
||||
### 3. 麒麟系统优化
|
||||
- 使用本地高速存储
|
||||
- 配置合适的文件系统权限
|
||||
- 定期清理临时文件
|
||||
|
||||
## 安全考虑
|
||||
|
||||
### 1. 文件安全
|
||||
- 验证上传文件类型
|
||||
- 限制文件大小
|
||||
- 扫描恶意文件
|
||||
|
||||
### 2. 路径安全
|
||||
- 防止路径遍历攻击
|
||||
- 使用相对路径存储
|
||||
- 验证文件扩展名
|
||||
|
||||
### 3. 权限控制
|
||||
- 基于角色的访问控制
|
||||
- 操作日志记录
|
||||
- 敏感数据脱敏
|
||||
|
||||
## 测试用例
|
||||
|
||||
### 1. 功能测试
|
||||
- [x] 导出带图片的Excel文件
|
||||
- [x] 导入含图片的Excel文件
|
||||
- [x] 图片格式验证
|
||||
- [x] 错误处理机制
|
||||
|
||||
### 2. 性能测试
|
||||
- [ ] 大量数据导出性能
|
||||
- [ ] 大量图片处理性能
|
||||
- [ ] 并发访问压力测试
|
||||
|
||||
### 3. 兼容性测试
|
||||
- [x] 不同Excel版本兼容性
|
||||
- [x] 不同图片格式支持
|
||||
- [x] 麒麟系统环境测试
|
||||
|
||||
## 维护指南
|
||||
|
||||
### 1. 日常维护
|
||||
- 监控磁盘空间使用
|
||||
- 清理过期临时文件
|
||||
- 检查错误日志
|
||||
|
||||
### 2. 故障排查
|
||||
- 检查文件系统权限
|
||||
- 验证图片路径正确性
|
||||
- 分析异常日志信息
|
||||
|
||||
### 3. 性能监控
|
||||
- 监控内存使用情况
|
||||
- 跟踪处理时间
|
||||
- 分析并发性能
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 1. 图片处理增强
|
||||
- 图片压缩
|
||||
- 格式转换
|
||||
- 水印添加
|
||||
|
||||
### 2. 存储扩展
|
||||
- 分布式文件存储
|
||||
- 对象存储集成
|
||||
- 图片CDN加速
|
||||
|
||||
### 3. 功能扩展
|
||||
- 批量图片处理
|
||||
- 图片预览功能
|
||||
- 历史版本管理
|
||||
@ -0,0 +1,123 @@
|
||||
package com.os.ems.info.domain;
|
||||
|
||||
import com.os.common.annotation.Excel;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 备件库记录导入导出VO对象
|
||||
*
|
||||
* @author Yinq
|
||||
* @date 2024-12-13
|
||||
*/
|
||||
public class SparePartsInventoryExportVO
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 主键 */
|
||||
@Excel(name = "主键")
|
||||
private Long objid;
|
||||
|
||||
/** 入库时间 */
|
||||
@Excel(name = "入库时间")
|
||||
private String warehouseDate;
|
||||
|
||||
/** 采购方式 */
|
||||
@Excel(name = "采购方式")
|
||||
private String purchaseMethod;
|
||||
|
||||
/** 库存位置 */
|
||||
@Excel(name = "库存位置")
|
||||
private String storageLocation;
|
||||
|
||||
/** 物品名称 */
|
||||
@Excel(name = "物品名称")
|
||||
private String itemName;
|
||||
|
||||
/** 原厂编号 */
|
||||
@Excel(name = "原厂编号")
|
||||
private String originalPartNumber;
|
||||
|
||||
/** 型号 */
|
||||
@Excel(name = "型号")
|
||||
private String model;
|
||||
|
||||
/** 入库数量 */
|
||||
@Excel(name = "入库数量")
|
||||
private Long warehouseQuantity;
|
||||
|
||||
/** 剩余数量 */
|
||||
@Excel(name = "剩余数量")
|
||||
private Long remainingQuantity;
|
||||
|
||||
/** 备注 */
|
||||
@Excel(name = "备注")
|
||||
private String remarks;
|
||||
|
||||
/** 动态盘点数据 - 用于存储各种盘点列的值 */
|
||||
private Map<String, Long> checkData = new HashMap<>();
|
||||
|
||||
// Getter和Setter方法
|
||||
public Long getObjid() { return objid; }
|
||||
public void setObjid(Long objid) { this.objid = objid; }
|
||||
|
||||
public String getWarehouseDate() { return warehouseDate; }
|
||||
public void setWarehouseDate(String warehouseDate) { this.warehouseDate = warehouseDate; }
|
||||
|
||||
public String getPurchaseMethod() { return purchaseMethod; }
|
||||
public void setPurchaseMethod(String purchaseMethod) { this.purchaseMethod = purchaseMethod; }
|
||||
|
||||
public String getStorageLocation() { return storageLocation; }
|
||||
public void setStorageLocation(String storageLocation) { this.storageLocation = storageLocation; }
|
||||
|
||||
public String getItemName() { return itemName; }
|
||||
public void setItemName(String itemName) { this.itemName = itemName; }
|
||||
|
||||
public String getOriginalPartNumber() { return originalPartNumber; }
|
||||
public void setOriginalPartNumber(String originalPartNumber) { this.originalPartNumber = originalPartNumber; }
|
||||
|
||||
public String getModel() { return model; }
|
||||
public void setModel(String model) { this.model = model; }
|
||||
|
||||
public Long getWarehouseQuantity() { return warehouseQuantity; }
|
||||
public void setWarehouseQuantity(Long warehouseQuantity) { this.warehouseQuantity = warehouseQuantity; }
|
||||
|
||||
public Long getRemainingQuantity() { return remainingQuantity; }
|
||||
public void setRemainingQuantity(Long remainingQuantity) { this.remainingQuantity = remainingQuantity; }
|
||||
|
||||
public String getRemarks() { return remarks; }
|
||||
public void setRemarks(String remarks) { this.remarks = remarks; }
|
||||
|
||||
public Map<String, Long> getCheckData() { return checkData; }
|
||||
public void setCheckData(Map<String, Long> checkData) { this.checkData = checkData; }
|
||||
|
||||
/** 动态设置盘点数据 */
|
||||
public void setCheckValue(String checkName, Long value) {
|
||||
this.checkData.put(checkName, value);
|
||||
}
|
||||
|
||||
/** 动态获取盘点数据 */
|
||||
public Long getCheckValue(String checkName) {
|
||||
return this.checkData.get(checkName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("objid", getObjid())
|
||||
.append("warehouseDate", getWarehouseDate())
|
||||
.append("purchaseMethod", getPurchaseMethod())
|
||||
.append("storageLocation", getStorageLocation())
|
||||
.append("itemName", getItemName())
|
||||
.append("originalPartNumber", getOriginalPartNumber())
|
||||
.append("model", getModel())
|
||||
.append("warehouseQuantity", getWarehouseQuantity())
|
||||
.append("remainingQuantity", getRemainingQuantity())
|
||||
.append("remarks", getRemarks())
|
||||
.append("checkData", getCheckData())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue