上传文件

① ② ③④⑤⑥⑦⑧⑨

1. 解决方案

1.1 原理规划

有以下解决思路

① 常规操作

  • 用户点击上传,将图片上传到imgs/temp目录下

  • 用户点击保存按钮后,将图片由temp目录移动到正常目录

  • 不点击保存按钮,temp 目录的文件,会按照时间,被自动化脚本自动删除。这样保证临时文件不会任意增长。

Base64操作

  • 前台调用JS函数,将本地文件转换成Base64格式,传递给后台
  • 后台保存到数据库中

这种操作不常用,因为这样效率太低,除非特殊情况。

  • 甲骨文的图片案例,这些图片是固定的,并且涉及到今后数据的迁移
    • 为了提高效率,可以在第一次读取的时候,将数据库中的图片,缓存到本地。

Base64转文件操作

方案①中,需要将文件上传到服务器上,为什么这么做呢?

  • 一、上传后可以在本地预览
  • 二、减少数据库缓存
  • 三、提前上传文件了,点击保存按钮时,减少一次性上传的压力。
  • 四、可以提前在后台进行图片压缩等功能

但是方案①也存在问题:

  • 提前把图片上传了,然后改变图片地址后,又重新显示出来,这样造成网络流量的浪费。

有没有改进方案:

  • 本地点击上传按钮,只在本地转成Base64进行预览。
  • 上传后,再将Base64转成服务器文件,而不是保存到数据库中,数据库中只保存路径。

1.2 使用场景

上传文件遇到的场景有:

  • 设置/权限管理/编辑管理员
    • 点击用户头像后,上传文件,并可以在前台预览。
    • 点击提交按钮后再提交。
  • 店铺/店铺帮助/编辑帮助
    • 可以上传正文的关联图片,这里上传完毕后,就实际保存了,不用等点击保存按钮。

2. 编辑帮助案例

2.1 说明

例如下面的案例,具体由以下功能:

  • 点击上传,上传一个图片,并在列表中现实。
  • 在列表中现实的是缩略图,点击后可以预览大图。

2.2 后台

  • 可以附加参数itemIduploadType
  • 后台进行缩略的功能
  • 返回的是一个对象,里面包含了缩略图与大图的地址
/**
* 上传图片文件,这些文件可能是新增上传的,也可能update上传的
* @param file
* @param itemId
* @param uploadType
* @return
*/
@Tag(name="帮助")
@Operation(summary = "上传文件")
@PostMapping("/uploadFile")
@AuthenticationType(type=AuthenticationType.authenticated)
public Upload uploadFile(@RequestParam("file") MultipartFile file
,@RequestParam Integer itemId
, @RequestParam Integer uploadType
){
String subPath = Global.ATTACH_ARTICLE;
// 还有关于文件格式限制、文件大小限制,详见:中配置。
Path unixPath= storageService.store(file,subPath,storageService.generateRandomFilename());
// 将图片压缩成60*60的图片
try {
Thumbnails.of(unixPath.toFile()).size(80, 80).toFile(getThumbnailsName(unixPath.toString()));
}catch (IOException e){
throw new BusinessException("缩略图压缩错误:"+e.toString());
}
// 得到http可以访问的路径
String httpUrlPath = StringUtils.replace(unixPath.toString()
,storageService.getImgRootLocation().toString(),"");
String httpUrlThumbnailsPath=StringUtils.replace(getThumbnailsName(unixPath.toString())
,storageService.getImgRootLocation().toString(),"");
// 加上/imgs前缀
String fileUrl= storageProperties.getImgDirName()+httpUrlPath;
Upload upload=new Upload();
upload.setFileName( fileUrl);
upload.setFileThumb(storageProperties.getImgDirName()+httpUrlThumbnailsPath);
upload.setItemId(itemId);
upload.setUploadType(uploadType);
upload.setFileSize(file.getSize());
uploadService.insertSelective(upload);
return upload;
}
private String getThumbnailsName(String oldName){
int splitIndex=oldName.lastIndexOf(".");
String first= oldName.substring(0,splitIndex)+":60-60";
return first+ oldName.substring(splitIndex,oldName.length());
}

2.3 前台

① 引用

// 上传文件
import { ImgFallback, getSingleUploadImgProps } from '@/services/Common';
import type { UploadEndPros } from '@/services/Common';

② 定义状态

核心是定义uploadProps中的onUploadEnd 上传完毕后的方法,其中para.response 是服务器返回的内容。

这个例子的内容比较复杂,是把服务器返回的图片地址,添加到一个数组中,并刷新页面。

// 上传文件
const [uploading, setUploading] = React.useState(false);
// 构造上传组件的属性
const uploadProps = getSingleUploadImgProps({
actionUrl: '/store/help/uploadFile',
setUploading,
onUploadEnd: (para: UploadEndPros<Store.Upload>) => {
if (para.success && para.response) {
setUploadArray((values: Store.Upload[]): Store.Upload[] => {
// 数组类型的state必须要用函数,才可以更新
const newArray: Store.Upload[] = [];
for (let i = 0; i < values.length; i += 1) {
newArray.push(values[i]);
}
if (para.response) {
newArray.push(para.response);
}
return newArray;
});
}
},
});

③ 配置组件

这里配置了传递参数与disabled状态。

<Upload
{...uploadProps}
// 后台需要的附加参数
data={{ itemId, uploadType }}
disabled={uploading}
>
<div className={styles.upload}>
<PlusOutlined />
<div className={styles.addText}>上传</div>
</div>
</Upload>