elephant-guide 后端文件(图片/附件)上传、下载、管理完整指南。仅在用户提及 elephant-guide 项目相关文件操作(上传、头像、图片显示、附件管理、filemedia、FileMedia、文件缓存、入库等)时触发。不适用于 AtroSnow 老项目(.c 后缀接口)。
组件路径文件 Controllercontroller/sysBaseData/FileMediaController.java文件 Serviceservice/sysBaseData/I/IFileMediaService.java文件实体entity/sysBaseData/FileMedia.java缓存类型枚举definition/sysBaseData/FileCacheType.java模块类型枚举definition/sysBaseData/FileMediaModule.java文件工具类kit/file/FileKit.java文件管理器(文件夹名常量)kit/file/FileManager.java
用户类型FileCacheType接口路径Staff(员工/后台管理)10POST /filemedia/b/saveFileMediaCacheCustomer(客户/前台)30POST /filemedia/f/saveFileMediaCacheCenter(中心)20POST /filemedia/c/saveFileMediaCacheArtist(艺术家)40POST /filemedia/a/saveFileMediaCache
text
POST /filemedia/{suffix}/saveFileMediaCache
Content-Type: multipart/form-data
file -- MultipartFile, 必填, 要上传的文件
userID -- String, 可选, 操作人ID(可从 Header Authorization 提取)
uploadKey -- String, 可选, 上传批次标识
groupUUID -- String, 可选, 文件分组UUID
itemNumber -- String, 可选, 默认 "1"
json
{
"code": 200,
"fileUUID": "temp_f3a2b1c4d5e6...",
"fileMediaID": null,
"msg": "上传成功"
}
关键返回值是 fileUUID,后续保存正式文件时需要用到。
javascript
// 使用 Element Plus el-upload
const uploadFile = async (file) => {
const formData = new FormData()
formData.append('file', file.raw)
// 注意:不需要包裹 params JSON,直接传独立参数
formData.append('userID', localStorage.getItem('staffUserID') || '')
const res = await axios.post('/filemedia/b/saveFileMediaCache', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
if (res.data.code === 200) {
return res.data.fileUUID // 返回临时文件UUID
}
throw new Error(res.data.msg || '上传失败')
}
text
GET /filemedia/{suffix}/downloadFileMedia
参数类型说明fileMediaIDString正式文件ID(正式文件才有)fileUUIDString临时文件UUID(临时文件用,缓存文件)imageLevelString图片规格:0=原图, 1=缩略图, 2=中等图userIDString可选
优先级: fileMediaID > fileUUID
vue
<template>
<!-- 方式1:直接拼接接口地址(推荐用于正式文件) -->
<el-image
:src="`/filemedia/b/downloadFileMedia?fileMediaID=${fileMediaID}&imageLevel=1`"
fit="cover"
/>
<!-- 方式2:临时文件(上传后未正式入库前) -->
<el-image
:src="`/filemedia/b/downloadFileMedia?fileUUID=${fileUUID}&imageLevel=1`"
fit="cover"
/>
</template>
imageLevel含义0原图1缩略图(小)2中等图
核心原则:不要在 Controller 层调用文件入库,应在业务 Service 保存业务数据时,调用 IFileMediaService.createFileMedia(),在回调中把生成的 fileMediaID 写入引用该文件的业务表或中间表。
签名一:单文件(传入 File 对象)
java
int createFileMedia(
File cacheFile, // 缓存目录中的临时文件(File对象)
FileMediaModule module, // 模块类型枚举
int userType, // FileCacheType 值(10/20/30/40)
String sysUserID, // 操作人ID
HashMap<String, Object> responseMap,
FileMediaCreateCallback callback // 回调,savingFileMedia(fileMediaId, fileMedia)
)
签名二:批量(传入 fileUUID 列表)
java
int createFileMedia(
int fileCacheType, // FileCacheType 值(10/20/30/40)
List<FileMediaCreateInfor> infos, // 包含 fileUUID 和 module 信息
String sysUserID,
HashMap<String, Object> responseMap,
FileMediaListCreateCallback callback // 回调,savingFileMediaList(List<FileMediaCreateInfor>)
)
java
// 单文件回调
public interface FileMediaCreateCallback {
void savingFileMedia(String fileMediaId, FileMedia fileMedia);
}
// 批量回调
public interface FileMediaListCreateCallback {
void savingFileMediaList(List<FileMediaCreateInfor> infos);
}
java
// 在业务 Service 的保存方法内,调用 createFileMedia 并在回调中关联 fileMediaID
@Autowired
private IFileMediaService fileMediaService;
public void saveCityWithCover(Map<String, String> params, String staffUserID) {
String cityID = params.get("cityID");
String fileUUID = params.get("fileUUID"); // 前端传入的临时文件UUID
City city = cityDAO.findById(cityID);
city.setName(params.get("name"));
if (fileUUID != null && !fileUUID.isEmpty()) {
// 1. 准备入库信息
FileMediaCreateInfor info = new FileMediaCreateInfor();
info.setFileUUID(fileUUID);
info.setModule(FileMediaModule.CITY_COVER); // 使用模块枚举
List<FileMediaCreateInfor> infos = new ArrayList<>();
infos.add(info);
HashMap<String, Object> responseMap = new HashMap<>();
// 2. 调用 createFileMedia,在回调中完成关联
fileMediaService.createFileMedia(
FileCacheType.FILE_CACHE_TYPE_STAFF, // 10,员工类型
infos,
staffUserID,
responseMap,
new FileMediaListCreateCallback() {
@Override
public void savingFileMediaList(List<FileMediaCreateInfor> savedInfos) {
// 3. 回调中:fileMediaID 已生成,写入业务表
for (FileMediaCreateInfor saved : savedInfos) {
city.setCoverFileMediaID(saved.getOriginalFileMediaId());
city.setUpdateTime(new Date());
city.setUpdateUID(staffUserID);
cityDAO.update(city);
}
}
}
);
} else {
cityDAO.save(city);
}
}
如果临时文件已从缓存中取出为 File 对象,可用签名一:
java
// 先获取临时文件
File cacheFile = new File(fileMediaService.getCacheFilePath(fileUUID, FileCacheType.FILE_CACHE_TYPE_STAFF));
fileMediaService.createFileMedia(
cacheFile,
FileMediaModule.CITY_COVER,
FileCacheType.FILE_CACHE_TYPE_STAFF,
staffUserID,
responseMap,
(fileMediaId, fileMedia) -> {
// 在回调中关联
city.setCoverFileMediaID(fileMediaId);
cityDAO.update(city);
}
);
/filemedia/b/saveFileMediaFile 是文件管理模块自己用的异步批量入库接口,不适合在业务 Service 中使用。它通过 saveFileKey 轮询进度,返回 fileMediaID,属于另一种独立流程。
枚举名valuefolderprefix说明SYSTEM10systemSCB系统文件STAFF_USER_HEAD20staffUserSHP用户头像CUSTOMER_USER_HEAD30customerUserCHP客户头像SHOP_PRODUCT40shopProductSPD产品文件CITY_COVER50cityCoverCVP城市封面图ART_FILE60artFileATF作品图CENTER_FILE70centerFileCHF门店资质附件ARTIST_USER_HEAD80artistUserAHP创作者头像APP_FILE90appFileAPFAPP文件
在 FileMediaModule 枚举中添加新枚举值,需设置4个属性:
java
// 1. value — 模块值,10的整数倍,项目内不重复
// 2. folder — 临时文件保存的文件夹名(对应 FileManager 中的 FOLD_NAME_XXX)
// 3. prefix — 文件名前缀,3个大写字母,项目内唯一
// 4. description — 模块描述
同时在 FileManager 中添加文件夹名常量。
java
public class FileCacheType {
public static final int FILE_CACHE_TYPE_STAFF = 10; // 员工/后台
public static final int FILE_CACHE_TYPE_CENTER = 20; // 中心
public static final int FILE_CACHE_TYPE_CUSTOMER = 30; // 客户
public static final int FILE_CACHE_TYPE_ARTIST = 40; // 艺术家
}
错误现象可能原因405 Method Not Allowed接口路径错误,检查是否多了 .c 后缀文件上传返回 fileUUID=nullfile 参数名不对,必须是 file下载图片是空白/404fileUUID 临时文件已过期(24h),需重新上传图片显示不完整imageLevel 设为 0 试试是否为原图正式入库失败fileUUID 已被使用或已过期
text
1. 前端上传文件
POST /filemedia/b/saveFileMediaCache
→ 获得 fileUUID
2. 前端提交业务表单(fileUUID + 业务数据)
POST /xxx/b/saveXXX
3. 后端保存业务数据,同时将 fileUUID 转为正式文件
POST /filemedia/b/saveFileMediaFile
→ 获得 fileMediaID
4. 前端展示图片
GET /filemedia/b/downloadFileMedia?fileMediaID=${fileMediaID}&imageLevel=1
file 参数,不要包裹 JSON