AtroSnow File Management

Other

elephant-guide 后端文件(图片/附件)上传、下载、管理完整开发指南。 当用户提到 elephant-guide 项目的文件上传、头像、图片显示、附件管理、filemedia、FileMedia、文件缓存、文件正式入库等场景时触发本技能。 不适用于 AtroSnow 老项目(.c 后缀接口的项目)。

Install

openclaw skills install atrosnow-file-management

AtroSnow 文件管理系统(elephant-guide)

何时使用

  • 为 elephant-guide 后端模块新增图片/附件上传功能
  • 前端需要上传文件(头像、产品图、城市封面等)
  • 需要在业务保存时将临时文件转为正式文件
  • 需要展示/下载已上传的文件
  • 需要理解 FileMediaModule 的定义和新增方式

核心组件速查

组件路径
文件 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

一、文件上传(临时文件)

1.1 各端上传接口路径

用户类型FileCacheType接口路径
Staff(员工/后台管理)10POST /filemedia/b/saveFileMediaCache
Customer(客户/前台)30POST /filemedia/f/saveFileMediaCache
Center(中心)20POST /filemedia/c/saveFileMediaCache
Artist(艺术家)40POST /filemedia/a/saveFileMediaCache

1.2 请求参数

POST /filemedia/{suffix}/saveFileMediaCache
Content-Type: multipart/form-data

file        -- MultipartFile, 必填, 要上传的文件
userID      -- String, 可选, 操作人ID(可从 Header Authorization 提取)
uploadKey   -- String, 可选, 上传批次标识
groupUUID   -- String, 可选, 文件分组UUID
itemNumber  -- String, 可选, 默认 "1"

1.3 响应示例

{
  "code": 200,
  "fileUUID": "temp_f3a2b1c4d5e6...",
  "fileMediaID": null,
  "msg": "上传成功"
}

关键返回值是 fileUUID,后续保存正式文件时需要用到。

1.4 前端上传示例(管理后台 Staff)

// 使用 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 || '上传失败')
}

二、图片/文件下载与展示

2.1 下载接口(所有端通用)

GET /filemedia/{suffix}/downloadFileMedia
参数类型说明
fileMediaIDString正式文件ID(正式文件才有)
fileUUIDString临时文件UUID(临时文件用,缓存文件)
imageLevelString图片规格:0=原图, 1=缩略图, 2=中等图
userIDString可选

优先级fileMediaID > fileUUID

2.2 前端展示图片

<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>

2.3 imageLevel 规格说明

imageLevel含义
0原图
1缩略图(小)
2中等图

三、文件正式入库(业务服务内调用 createFileMedia)

核心原则:不要在 Controller 层调用文件入库,应在业务 Service 保存业务数据时,调用 IFileMediaService.createFileMedia(),在回调中把生成的 fileMediaID 写入引用该文件的业务表或中间表。

3.1 两种 createFileMedia 签名

签名一:单文件(传入 File 对象)

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 列表)

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>)
)

3.2 回调接口

// 单文件回调
public interface FileMediaCreateCallback {
    void savingFileMedia(String fileMediaId, FileMedia fileMedia);
}

// 批量回调
public interface FileMediaListCreateCallback {
    void savingFileMediaList(List<FileMediaCreateInfor> infos);
}

3.3 业务保存流程示例

// 在业务 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);
    }
}

3.4 另一种方式:单文件直接传 File 对象

如果临时文件已从缓存中取出为 File 对象,可用签名一:

// 先获取临时文件
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);
    }
);

3.5 saveFileMediaFile 接口(文件管理后台专用)

/filemedia/b/saveFileMediaFile 是文件管理模块自己用的异步批量入库接口,不适合在业务 Service 中使用。它通过 saveFileKey 轮询进度,返回 fileMediaID,属于另一种独立流程。


四、FileMediaModule 模块定义

4.1 已有模块

枚举名valuefolderprefix说明
SYSTEM10systemSCB系统文件
STAFF_USER_HEAD20staffUserSHP用户头像
CUSTOMER_USER_HEAD30customerUserCHP客户头像
SHOP_PRODUCT40shopProductSPD产品文件
CITY_COVER50cityCoverCVP城市封面图
ART_FILE60artFileATF作品图
CENTER_FILE70centerFileCHF门店资质附件
ARTIST_USER_HEAD80artistUserAHP创作者头像
APP_FILE90appFileAPFAPP文件

4.2 新增模块步骤

FileMediaModule 枚举中添加新枚举值,需设置4个属性:

// 1. value — 模块值,10的整数倍,项目内不重复
// 2. folder — 临时文件保存的文件夹名(对应 FileManager 中的 FOLD_NAME_XXX)
// 3. prefix — 文件名前缀,3个大写字母,项目内唯一
// 4. description — 模块描述

同时在 FileManager 中添加文件夹名常量。


五、FileCacheType 值定义

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 已被使用或已过期

七、完整开发流程

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

八、开发检查清单

  • 确认使用正确的用户类型端(/b/ /f/ /c/ /a/)
  • 上传接口直接传 file 参数,不要包裹 JSON
  • fileUUID 有效期 24 小时,及时完成正式入库
  • 图片展示用 downloadFileMedia 接口,不要自己写 Controller
  • 新增 FileMediaModule 时同时更新 FileManager 常量
  • FileCacheType 值用 10/20/30/40,不是 1/2/3/4