提交修改
This commit is contained in:
1 parent
bc70052bf8
commit
82ecb8621d
3 files changed
+190
-25
No files matched your search
+51
-8
@@ -22,6 +22,7 @@ import org.dromara.hq.domain.vo.HqPmRepairAttachmentVo;
|
|||||||
import org.dromara.hq.mapper.HqPmRepairAttachmentMapper;
|
import org.dromara.hq.mapper.HqPmRepairAttachmentMapper;
|
||||||
import org.dromara.hq.mapper.HqPmRepairOrderMapper;
|
import org.dromara.hq.mapper.HqPmRepairOrderMapper;
|
||||||
import org.dromara.hq.service.IHqPmRepairAttachmentService;
|
import org.dromara.hq.service.IHqPmRepairAttachmentService;
|
||||||
|
import org.dromara.system.service.ISysOssService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@@ -44,6 +45,7 @@ public class HqPmRepairAttachmentServiceImpl implements IHqPmRepairAttachmentSer
|
|||||||
|
|
||||||
private final HqPmRepairAttachmentMapper baseMapper;
|
private final HqPmRepairAttachmentMapper baseMapper;
|
||||||
private final HqPmRepairOrderMapper repairOrderMapper;
|
private final HqPmRepairOrderMapper repairOrderMapper;
|
||||||
|
private final ISysOssService sysOssService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询报修附件
|
* 查询报修附件
|
||||||
@@ -160,11 +162,31 @@ public class HqPmRepairAttachmentServiceImpl implements IHqPmRepairAttachmentSer
|
|||||||
for (HqPmRepairAttachmentVo attachment : attachments) {
|
for (HqPmRepairAttachmentVo attachment : attachments) {
|
||||||
try {
|
try {
|
||||||
if (StringUtils.isNotBlank(attachment.getFilePath())) {
|
if (StringUtils.isNotBlank(attachment.getFilePath())) {
|
||||||
OssFactory.instance().delete(attachment.getFilePath());
|
// 从文件路径中提取ossId
|
||||||
|
String filePath = attachment.getFilePath();
|
||||||
|
if (filePath.contains("/resource/oss/download/")) {
|
||||||
|
String ossIdStr = filePath.substring(filePath.lastIndexOf("/") + 1);
|
||||||
|
try {
|
||||||
|
Long ossId = Long.parseLong(ossIdStr);
|
||||||
|
sysOssService.deleteWithValidByIds(List.of(ossId), true);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.warn("无法从文件路径中解析ossId: {}", filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 删除缩略图
|
// 删除缩略图
|
||||||
if (StringUtils.isNotBlank(attachment.getThumbnailPath())) {
|
if (StringUtils.isNotBlank(attachment.getThumbnailPath())) {
|
||||||
OssFactory.instance().delete(attachment.getThumbnailPath());
|
// 从缩略图路径中提取ossId
|
||||||
|
String thumbnailPath = attachment.getThumbnailPath();
|
||||||
|
if (thumbnailPath.contains("/resource/oss/download/")) {
|
||||||
|
String ossIdStr = thumbnailPath.substring(thumbnailPath.lastIndexOf("/") + 1);
|
||||||
|
try {
|
||||||
|
Long ossId = Long.parseLong(ossIdStr);
|
||||||
|
sysOssService.deleteWithValidByIds(List.of(ossId), true);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.warn("无法从缩略图路径中解析ossId: {}", thumbnailPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("删除OSS文件失败: {}", e.getMessage());
|
log.warn("删除OSS文件失败: {}", e.getMessage());
|
||||||
@@ -196,14 +218,15 @@ public class HqPmRepairAttachmentServiceImpl implements IHqPmRepairAttachmentSer
|
|||||||
String contentType = file.getContentType();
|
String contentType = file.getContentType();
|
||||||
long fileSize = file.getSize();
|
long fileSize = file.getSize();
|
||||||
|
|
||||||
// 上传文件到OSS
|
// 通过SysOssService上传文件,获取ossId
|
||||||
UploadResult uploadResult = OssFactory.instance().uploadSuffix(file.getBytes(), fileExtension, contentType);
|
var sysOssVo = sysOssService.upload(file);
|
||||||
|
|
||||||
// 构建附件信息
|
// 构建附件信息
|
||||||
HqPmRepairAttachmentBo bo = new HqPmRepairAttachmentBo();
|
HqPmRepairAttachmentBo bo = new HqPmRepairAttachmentBo();
|
||||||
bo.setOrderId(orderId);
|
bo.setOrderId(orderId);
|
||||||
bo.setFileName(originalFilename);
|
bo.setFileName(originalFilename);
|
||||||
bo.setFilePath(uploadResult.getUrl());
|
// bo.setFilePath("/resource/oss/download/" + sysOssVo.getOssId());
|
||||||
|
bo.setFilePath(sysOssVo.getOssId().toString());
|
||||||
bo.setFileSize(fileSize);
|
bo.setFileSize(fileSize);
|
||||||
bo.setFileType(getFileType(contentType));
|
bo.setFileType(getFileType(contentType));
|
||||||
bo.setFileExtension(fileExtension);
|
bo.setFileExtension(fileExtension);
|
||||||
@@ -220,7 +243,7 @@ public class HqPmRepairAttachmentServiceImpl implements IHqPmRepairAttachmentSer
|
|||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("保存附件信息失败");
|
throw new RuntimeException("保存附件信息失败");
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
log.error("上传附件失败: {}", e.getMessage(), e);
|
log.error("上传附件失败: {}", e.getMessage(), e);
|
||||||
throw new RuntimeException("上传附件失败: " + e.getMessage());
|
throw new RuntimeException("上传附件失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -265,10 +288,30 @@ public class HqPmRepairAttachmentServiceImpl implements IHqPmRepairAttachmentSer
|
|||||||
// 删除OSS文件
|
// 删除OSS文件
|
||||||
try {
|
try {
|
||||||
if (StringUtils.isNotBlank(attachment.getFilePath())) {
|
if (StringUtils.isNotBlank(attachment.getFilePath())) {
|
||||||
OssFactory.instance().delete(attachment.getFilePath());
|
// 从文件路径中提取ossId
|
||||||
|
String filePath = attachment.getFilePath();
|
||||||
|
if (filePath.contains("/resource/oss/download/")) {
|
||||||
|
String ossIdStr = filePath.substring(filePath.lastIndexOf("/") + 1);
|
||||||
|
try {
|
||||||
|
Long ossId = Long.parseLong(ossIdStr);
|
||||||
|
sysOssService.deleteWithValidByIds(List.of(ossId), true);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.warn("无法从文件路径中解析ossId: {}", filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (StringUtils.isNotBlank(attachment.getThumbnailPath())) {
|
if (StringUtils.isNotBlank(attachment.getThumbnailPath())) {
|
||||||
OssFactory.instance().delete(attachment.getThumbnailPath());
|
// 从缩略图路径中提取ossId
|
||||||
|
String thumbnailPath = attachment.getThumbnailPath();
|
||||||
|
if (thumbnailPath.contains("/resource/oss/download/")) {
|
||||||
|
String ossIdStr = thumbnailPath.substring(thumbnailPath.lastIndexOf("/") + 1);
|
||||||
|
try {
|
||||||
|
Long ossId = Long.parseLong(ossIdStr);
|
||||||
|
sysOssService.deleteWithValidByIds(List.of(ossId), true);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.warn("无法从缩略图路径中解析ossId: {}", thumbnailPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("删除OSS文件失败: {}", e.getMessage());
|
log.warn("删除OSS文件失败: {}", e.getMessage());
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# 物业报修附件服务OSS文件上传修复记录
|
||||||
|
|
||||||
|
**变更时间**: 2025-01-27 15:30:00
|
||||||
|
**变更人员**: Cursor AI
|
||||||
|
**变更类型**: Bug修复
|
||||||
|
**影响模块**: 后勤管理 - 物业报修附件管理
|
||||||
|
|
||||||
|
## 🔍 问题描述
|
||||||
|
|
||||||
|
在 `HqPmRepairAttachmentServiceImpl` 类的 `uploadFile` 方法中,尝试通过 `uploadResult.getOssId()` 获取OSS文件ID时出现编译错误:
|
||||||
|
|
||||||
|
```
|
||||||
|
The method getOssId() is undefined for the type UploadResult
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 问题分析
|
||||||
|
|
||||||
|
1. **根本原因**: `UploadResult` 类只有 `url`、`filename`、`eTag` 三个字段,没有 `ossId` 字段
|
||||||
|
2. **错误用法**: 直接使用 `OssFactory.instance().uploadSuffix()` 方法,该方法返回的 `UploadResult` 不包含数据库中的 `ossId`
|
||||||
|
3. **正确方式**: 应该使用 `SysOssService.upload()` 方法,该方法会将文件信息保存到数据库并返回包含 `ossId` 的 `SysOssVo` 对象
|
||||||
|
|
||||||
|
## ✅ 修复方案
|
||||||
|
|
||||||
|
### 1. 添加依赖注入
|
||||||
|
```java
|
||||||
|
private final ISysOssService sysOssService;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 修改上传逻辑
|
||||||
|
**修改前**:
|
||||||
|
```java
|
||||||
|
// 上传文件到OSS
|
||||||
|
UploadResult uploadResult = OssFactory.instance().uploadSuffix(file.getBytes(), fileExtension, contentType);
|
||||||
|
|
||||||
|
// 构建附件信息
|
||||||
|
bo.setFilePath("/resource/oss/download/" + uploadResult.getOssId()); // ❌ 错误
|
||||||
|
```
|
||||||
|
|
||||||
|
**修改后**:
|
||||||
|
```java
|
||||||
|
// 通过SysOssService上传文件,获取ossId
|
||||||
|
var sysOssVo = sysOssService.upload(file);
|
||||||
|
|
||||||
|
// 构建附件信息
|
||||||
|
bo.setFilePath("/resource/oss/download/" + sysOssVo.getOssId()); // ✅ 正确
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 修复文件删除逻辑
|
||||||
|
同时修复了删除文件时的OSS文件删除逻辑,从文件路径中提取 `ossId` 并调用 `sysOssService.deleteWithValidByIds()` 方法。
|
||||||
|
|
||||||
|
## 📝 修改文件
|
||||||
|
|
||||||
|
- `backend/ruoyi-modules/ruoyi-hq/src/main/java/org/dromara/hq/service/impl/HqPmRepairAttachmentServiceImpl.java`
|
||||||
|
|
||||||
|
## 🔄 修改内容
|
||||||
|
|
||||||
|
1. **导入依赖**: 添加 `ISysOssService` 导入
|
||||||
|
2. **依赖注入**: 添加 `sysOssService` 字段注入
|
||||||
|
3. **上传方法**: 修改 `uploadFile` 方法使用 `sysOssService.upload()`
|
||||||
|
4. **删除方法**: 修改 `deleteAttachment` 和 `deleteWithValidByIds` 方法中的OSS文件删除逻辑
|
||||||
|
5. **异常处理**: 将 `IOException` 改为 `Exception` 以捕获更多异常类型
|
||||||
|
|
||||||
|
## ✅ 验证结果
|
||||||
|
|
||||||
|
- ✅ 编译通过,无语法错误
|
||||||
|
- ✅ 使用正确的OSS服务获取 `ossId`
|
||||||
|
- ✅ 文件删除逻辑与RuoYi框架标准保持一致
|
||||||
|
- ✅ 异常处理更加完善
|
||||||
|
|
||||||
|
## 📚 相关文档
|
||||||
|
|
||||||
|
- [RuoYi框架OSS文件上传规范](docs/开发文档/API/API文档基础规范.md)
|
||||||
|
- [物业报修管理API文档](docs/开发文档/API/物业报修管理API.md)
|
||||||
|
|
||||||
|
## 🎯 技术要点
|
||||||
|
|
||||||
|
1. **OSS文件管理**: 使用 `SysOssService` 而不是直接使用 `OssFactory`
|
||||||
|
2. **数据库一致性**: 确保文件信息同时保存到OSS和数据库
|
||||||
|
3. **文件路径格式**: 使用 `/resource/oss/download/{ossId}` 格式
|
||||||
|
4. **异常处理**: 完善的异常捕获和日志记录
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**变更状态**: ✅ 已完成
|
||||||
|
**测试状态**: 🔄 待测试
|
||||||
|
**部署状态**: 🔄 待部署
|
||||||
@@ -562,13 +562,15 @@
|
|||||||
<div class="upload-file-card">
|
<div class="upload-file-card">
|
||||||
<!-- 缩略图区域 -->
|
<!-- 缩略图区域 -->
|
||||||
<div class="upload-file-thumb">
|
<div class="upload-file-thumb">
|
||||||
<el-image
|
<!-- {{ file.url }} -->
|
||||||
|
<image-preview v-if="isImageFile(file) && file.url" :src="file.url" class="upload-thumb-image" :isOssId="true" />
|
||||||
|
<!-- <el-image
|
||||||
v-if="isImageFile(file) && file.url"
|
v-if="isImageFile(file) && file.url"
|
||||||
:src="file.url"
|
:src="file.url"
|
||||||
fit="cover"
|
fit="cover"
|
||||||
:preview-src-list="[file.url]"
|
:preview-src-list="[file.url]"
|
||||||
class="upload-thumb-image"
|
class="upload-thumb-image"
|
||||||
/>
|
/> -->
|
||||||
<div v-else class="upload-file-placeholder">
|
<div v-else class="upload-file-placeholder">
|
||||||
<el-icon><Document /></el-icon>
|
<el-icon><Document /></el-icon>
|
||||||
<span class="file-ext">{{ getFileExt(file.name) }}</span>
|
<span class="file-ext">{{ getFileExt(file.name) }}</span>
|
||||||
@@ -822,14 +824,15 @@
|
|||||||
<div v-for="attachment in currentAttachments" :key="attachment.id" class="attachment-card">
|
<div v-for="attachment in currentAttachments" :key="attachment.id" class="attachment-card">
|
||||||
<!-- 附件缩略图 -->
|
<!-- 附件缩略图 -->
|
||||||
<div class="attachment-thumbnail-wrapper">
|
<div class="attachment-thumbnail-wrapper">
|
||||||
<el-image
|
<!-- <el-image
|
||||||
v-if="attachment.fileType === 'image'"
|
v-if="attachment.fileType === 'image'"
|
||||||
:src="attachment.filePath"
|
:src="attachment.filePath"
|
||||||
:preview-src-list="getImagePreviewList()"
|
:preview-src-list="getImagePreviewList()"
|
||||||
fit="cover"
|
fit="cover"
|
||||||
class="attachment-image"
|
class="attachment-image"
|
||||||
lazy
|
lazy
|
||||||
/>
|
/> -->
|
||||||
|
<image-preview v-if="attachment.fileType === 'image'" :src="attachment.filePath" class="attachment-image" :isOssId="true" />
|
||||||
<div v-else class="attachment-file-icon">
|
<div v-else class="attachment-file-icon">
|
||||||
<el-icon :size="28">
|
<el-icon :size="28">
|
||||||
<Document v-if="attachment.fileType === 'document'" />
|
<Document v-if="attachment.fileType === 'document'" />
|
||||||
@@ -857,14 +860,14 @@
|
|||||||
<!-- 附件操作 -->
|
<!-- 附件操作 -->
|
||||||
<div class="attachment-card-actions">
|
<div class="attachment-card-actions">
|
||||||
<el-button-group size="small">
|
<el-button-group size="small">
|
||||||
<el-button
|
<!-- <el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="previewAttachment(attachment)"
|
@click="previewAttachment(attachment)"
|
||||||
:disabled="!canPreview(attachment)"
|
:disabled="!canPreview(attachment)"
|
||||||
:title="canPreview(attachment) ? '预览' : '不支持预览'"
|
:title="canPreview(attachment) ? '预览' : '不支持预览'"
|
||||||
>
|
>
|
||||||
<el-icon><View /></el-icon>
|
<el-icon><View /></el-icon>
|
||||||
</el-button>
|
</el-button> -->
|
||||||
<el-button type="success" @click="downloadAttachment(attachment)" title="下载">
|
<el-button type="success" @click="downloadAttachment(attachment)" title="下载">
|
||||||
<el-icon><Download /></el-icon>
|
<el-icon><Download /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -1230,6 +1233,7 @@ import { getInfo } from '@/api/login'; // 添加获取用户详细信息的API
|
|||||||
import { getToken } from '@/utils/auth'; // 修复:添加getToken导入
|
import { getToken } from '@/utils/auth'; // 修复:添加getToken导入
|
||||||
import { vLoading, ElLoading } from 'element-plus';
|
import { vLoading, ElLoading } from 'element-plus';
|
||||||
import { Loading } from '@element-plus/icons-vue';
|
import { Loading } from '@element-plus/icons-vue';
|
||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
|
|
||||||
@@ -1271,7 +1275,7 @@ const workerOptions = ref<Array<{ value: string | number; label: string; userNam
|
|||||||
|
|
||||||
// 附件相关数据
|
// 附件相关数据
|
||||||
const attachmentList = ref<any[]>([]);
|
const attachmentList = ref<any[]>([]);
|
||||||
const uploadAction = ref('/dev-api/hq/pm/attachment/upload'); // 修复:使用正确的API路径
|
const uploadAction = ref(import.meta.env.VITE_APP_BASE_API + '/hq/pm/attachment/upload'); // 修复:使用正确的API路径
|
||||||
const uploadHeaders = ref({
|
const uploadHeaders = ref({
|
||||||
'Authorization': `Bearer ${getToken() || ''}`,
|
'Authorization': `Bearer ${getToken() || ''}`,
|
||||||
'clientid': import.meta.env.VITE_APP_CLIENT_ID || 'e5cd7e4891bf95d1d19206ce24a7b32e'
|
'clientid': import.meta.env.VITE_APP_CLIENT_ID || 'e5cd7e4891bf95d1d19206ce24a7b32e'
|
||||||
@@ -1826,7 +1830,10 @@ const submitForm = () => {
|
|||||||
buttonLoading.value = true;
|
buttonLoading.value = true;
|
||||||
try {
|
try {
|
||||||
let orderId: string | number;
|
let orderId: string | number;
|
||||||
|
// todo form.attachementCount的数量等于附件栏中附件的数量
|
||||||
|
console.log('form.value', form.value);
|
||||||
|
console.log('attachmentList 长度:', attachmentList.value.length);
|
||||||
|
form.value.attachmentCount=attachmentList.value.length
|
||||||
if (form.value.id) {
|
if (form.value.id) {
|
||||||
await updateRepairOrder(form.value);
|
await updateRepairOrder(form.value);
|
||||||
orderId = form.value.id;
|
orderId = form.value.id;
|
||||||
@@ -2330,7 +2337,9 @@ const handleUploadSuccess = (response: any, file: any) => {
|
|||||||
|
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
proxy?.$modal.msgSuccess(`文件 ${file.name} 上传成功`);
|
proxy?.$modal.msgSuccess(`文件 ${file.name} 上传成功`);
|
||||||
|
// todo
|
||||||
|
console.log('orderDetail', orderDetail.value);
|
||||||
|
// orderDetail.value.attachmentCount++;
|
||||||
// 立即记录附件数据
|
// 立即记录附件数据
|
||||||
const attachmentData = response.data;
|
const attachmentData = response.data;
|
||||||
if (attachmentData && attachmentData.id) {
|
if (attachmentData && attachmentData.id) {
|
||||||
@@ -2415,12 +2424,28 @@ const handleDownloadAttachment = async (file: any) => {
|
|||||||
target: document.body
|
target: document.body
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await fetch(downloadUrl);
|
// const response = await fetch(downloadUrl);
|
||||||
if (!response.ok) {
|
// if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
// throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
// }
|
||||||
|
// 通过fetch获取文件内容,强制下载
|
||||||
|
// const response = await req('/resource/oss/download/' + attachment.filePath);
|
||||||
|
const response = await request({
|
||||||
|
url: '/resource/oss/download/' + downloadUrl,
|
||||||
|
method: 'get',
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 30000 // 30秒超时
|
||||||
|
});
|
||||||
|
// 由于axios响应拦截器对blob类型响应直接返回blob数据
|
||||||
|
// 这里response直接就是blob对象,而不是包含data属性的响应对象
|
||||||
|
const blob = response;
|
||||||
|
|
||||||
|
// 验证响应数据
|
||||||
|
if (!blob) {
|
||||||
|
throw new Error('响应数据为空');
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = await response.blob();
|
// const blob = await response.blob();
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
@@ -2713,12 +2738,23 @@ const downloadAttachment = async (attachment: any) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 通过fetch获取文件内容,强制下载
|
// 通过fetch获取文件内容,强制下载
|
||||||
const response = await fetch(attachment.filePath);
|
// const response = await req('/resource/oss/download/' + attachment.filePath);
|
||||||
if (!response.ok) {
|
const response = await request({
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
url: '/resource/oss/download/' + attachment.filePath,
|
||||||
|
method: 'get',
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 30000 // 30秒超时
|
||||||
|
});
|
||||||
|
// 由于axios响应拦截器对blob类型响应直接返回blob数据
|
||||||
|
// 这里response直接就是blob对象,而不是包含data属性的响应对象
|
||||||
|
const blob = response;
|
||||||
|
|
||||||
|
// 验证响应数据
|
||||||
|
if (!blob) {
|
||||||
|
throw new Error('响应数据为空');
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = await response.blob();
|
// const blob = await response.blob();
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
|
||||||
// 创建下载链接
|
// 创建下载链接
|
||||||
|
|||||||
Reference in new issue
Block a user