Nginx配置
# 公众端的附件上传
location /api/visitor/upload {
# Pass altered request body to this location
upload_pass /api/outerPortal/uploadAndSave;
# Store files to this directory
# The directory is hashed, subdirectories 0 1 2 3 4 5 6 7 8 9 should exist
upload_store /home/museum/data/fileupload/upload/visitor 1;
# Allow uploaded files to be read only by user
upload_store_access user:rw;
# Set specified fields in request body
upload_set_form_field "${upload_field_name}_name" $upload_file_name;
upload_set_form_field "${upload_field_name}_content_type" $upload_content_type;
upload_set_form_field "${upload_field_name}_path" $upload_tmp_path;
# Inform backend about hash and size of a file
upload_aggregate_form_field "${upload_field_name}_md5" $upload_file_md5;
upload_aggregate_form_field "${upload_field_name}_size" $upload_file_size;
upload_pass_form_field ".*";
upload_pass_form_field "modelName";
}
# Pass altered request body to a backend
location /api/outerPortal/uploadAndSave {
proxy_pass http://10.130.1.11:8250;
}
Springboot接口接受
package xxxx.museum.outerPortal.interfaces;
import xxxx.common.domain.model.Result;
import xxxx.common.domain.model.SessionUser;
import xxxx.museum.outerPortal.domain.model.FileStoreItemVisitor;
import xxxx.museum.outerPortal.service.UploadFileService;
import xxxx.visitor.common.VisitorBaseController;
import xxxx.visitor.common.domain.model.VisitorSessionUser;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 平台common文件上传暴露接口
*/
@RestController
@ConditionalOnClass(value = {org.thymeleaf.dialect.IExpressionObjectDialect.class})
@RequestMapping("api/outerPortal")
public class UploadController extends VisitorBaseController {
@Autowired
private UploadFileService uploadFileService;
/**
* 接收普通附件的上传
*/
@PostMapping("/uploadAndSave")
@ResponseBody
@ApiOperation(value = "上传文件", notes = "用户文件上传")
public Result uploadAndSave(@RequestParam(value = "modelName", required = false) String modelName,
@RequestParam(value = "files_name", required = false) String fileName,
@RequestParam(value = "files_path", required = false) String filePath,
@RequestParam(value = "files_size", required = false) Integer fileSize,
HttpServletRequest request,
HttpServletResponse reponse) {
Result msg = new Result();
msg.setSuccess(false);
VisitorSessionUser sessionUser = getSessionUser(request);
//将得到的用户对象
SessionUser user = new SessionUser();
user.setUserId(sessionUser.getMid());
if (StringUtils.isNotEmpty(fileName)) {
FileStoreItemVisitor itemVisitor = uploadFileService.save(sessionUser, modelName, fileName, filePath, fileSize);
if (itemVisitor != null) {
msg.setResponse(itemVisitor);
msg.setMsg("上传头像成功!");
msg.setSuccess(true);
} else {
msg.setMsg("上传失败,请重新上传!");
}
} else {
msg.setMsg("上传失败,请重新上传!");
}
return msg;
}
}
实现上传的机制
package xxxx.museum.outerPortal.service;
import cn.hutool.core.util.RuntimeUtil;
import com.alibaba.nacos.common.utils.UuidUtils;
import xxxx.common.domain.model.exception.BusinessException;
import xxxx.museum.outerPortal.domain.model.FileStoreItemVisitor;
import xxxx.visitor.common.domain.model.VisitorSessionUser;
import org.im4java.core.ConvertCmd;
import org.im4java.core.IMOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.Calendar;
import java.util.Date;
/**
* @author Administrator
* @apiNote 公众端文件上传
* @date 2024/6/5 14:20
*/
@Service
@SuppressWarnings("all")
public class UploadFileService {
private static String forbiddenExtension = "sh,bat,exe";
private final String fileSaveBasePath = "/home/museum/data/fileupload/common/";
private static String[] allowPictures = "bmp,jpeg,jpg,png".split(",");
private Logger logger = LoggerFactory.getLogger(getClass());
@Value("${visitor.html.base.path:http://10.130.1.11/fileupload/common/}")
private String htmlBasePath;
@Value("${visitor.image.magick.path:C:\\GraphicsMagick-1.3.36-Q16}")
private String imageMagickPath;
/**
* 过滤有害脚本文件
*
* @param fileExt fileExt
* @return boolean
*/
private boolean checkFileExtension(String fileExt) {
String[] forbiddenTypes = forbiddenExtension.split(",");
String[] var3 = forbiddenTypes;
int var4 = forbiddenTypes.length;
for (int var5 = 0; var5 < var4; ++var5) {
String s = var3[var5];
if (s.equalsIgnoreCase(fileExt)) {
return false;
}
}
return true;
}
public FileStoreItemVisitor save(VisitorSessionUser sessionUser, String modelName, String fileName, String filePath, Integer fileSize) {
String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
if (!this.checkFileExtension(fileExtension)) {
throw new BusinessException("不允许上传此类型文件:" + fileExtension);
} else {
// 年月日路径
String relativePath = this.getRelativeFolder(fileSaveBasePath);
// 生成本次文件移动的地址
String folderPath = fileSaveBasePath + relativePath;
// 生成文件名称
String saveFileName = UuidUtils.generateUuid() + "." + fileExtension;
// 生成全限定名称的文件声明
String saveFilePath = folderPath + saveFileName;
// Nginx Upload 文件 由暂存地址移动到当前路径全限定名
String result = RuntimeUtil.execForStr(new String[]{"mv", filePath, saveFilePath});
this.logger.info("文件移动结果:" + result);
FileStoreItemVisitor fileStoreItem = new FileStoreItemVisitor();
// 生成Nginx可访问的相对路径
fileStoreItem.setRelativeUrl(relativePath);
// 生成预览压缩图地址
fileStoreItem.setThumbUrl(this.generateThumbnail(htmlBasePath, saveFilePath, fileExtension));
logger.info("文件 Nginx 缩略图 路径:" + fileStoreItem.getThumbUrl());
fileStoreItem.setFileName(fileName);
fileStoreItem.setFileType(fileExtension);
fileStoreItem.setFileSize(fileSize);
fileStoreItem.setCreateTime(new Date());
fileStoreItem.setCreatorId(sessionUser.getUserId());
fileStoreItem.setCreator(sessionUser.getUserName());
// 生成下载地址
fileStoreItem.setHtmlUrl(htmlBasePath + relativePath + saveFileName);
logger.info("文件 Nginx 下载 路径:" + fileStoreItem.getHtmlUrl());
return fileStoreItem;
}
}
private String generateThumbnail(String htmlBasePath, String filePath, String fileExtension) {
String thumbnailUrl = "";
if (this.isPicture(fileExtension)) {
int idx = filePath.lastIndexOf(".");
if (idx > -1) {
String thumbnailPath = filePath.substring(0, idx) + "_thb." + fileExtension;
this.compressImage(filePath, thumbnailPath, 300, 300, 0.8F);
thumbnailUrl = htmlBasePath + thumbnailPath.replace(fileSaveBasePath, "");
}
} else {
thumbnailUrl = "/static/global/img/file/" + fileExtension.toLowerCase() + ".png";
}
return thumbnailUrl;
}
public void compressImage(String imagePath, String thumbnailPath, int width, int height, Float quality) {
IMOperation op = new IMOperation();
op.addImage();
String raw = width + "x" + height + "^";
ConvertCmd cmd = new ConvertCmd(true);
op.addRawArgs(new String[]{"-sample", raw});
if (quality != null && !quality.toString().equals("")) {
op.addRawArgs(new String[]{"-quality", quality.toString()});
}
op.addImage();
String osName = System.getProperty("os.name").toLowerCase();
if (osName.indexOf("win") != -1) {
cmd.setSearchPath(imageMagickPath);
} else {
cmd.setSearchPath("/usr/local/bin");
}
try {
cmd.run(op, new Object[]{imagePath, thumbnailPath});
} catch (Exception var12) {
this.logger.error("文件压缩失败", var12);
}
}
boolean isPicture(String fileExtension) {
String[] var2 = allowPictures;
int var3 = var2.length;
for (int var4 = 0; var4 < var3; ++var4) {
String s = var2[var4];
if (s.equalsIgnoreCase(fileExtension)) {
return true;
}
}
return false;
}
private String getRelativeFolder(String fileSavePath) {
String relativePath = this.getFolderByDate();
String path = fileSavePath + relativePath;
File tempPath = new File(path);
if (!tempPath.exists()) {
tempPath.mkdirs();
}
return relativePath;
}
private String getFolderByDate() {
StringBuilder builder = new StringBuilder();
Calendar now = Calendar.getInstance();
builder.append(now.get(1)).append("/").append(now.get(2) + 1).append("/").append(now.get(5)).append("/");
return builder.toString();
}
}
结果
http://10.130.1.11/fileupload/common/2024/6/5/5694f15f-b345-4efd-a97e-4774dd38f467.xlsx
{
"@type": "com.cztech.common.domain.model.Result",
"msg": "上传头像成功!",
"obj": {
"@type": "com.cztech.museum.outerPortal.domain.model.FileStoreItemVisitor",
"createTime": "2024-06-05 17:51:35",
"creatorId": "2365",
"fileName": "琴.jpeg",
"fileSize": 95263,
"fileType": "jpeg",
"htmlUrl": "http://10.130.1.11/fileupload/common/2024/6/5/",
"relativeUrl": "2024/6/5/",
"thumbUrl": "http://10.130.1.11/fileupload/common//home/museum/data/fileupload/common/2024/6/5/724440db-d1af-48dc-a67a-5e124e78b566_thb.jpeg"
},
"response": {
"@type": "com.cztech.museum.outerPortal.domain.model.FileStoreItemVisitor",
"createTime": "2024-06-05 17:51:35",
"creatorId": "2365",
"fileName": "琴.jpeg",
"fileSize": 95263,
"fileType": "jpeg",
"htmlUrl": "http://10.130.1.11/fileupload/common/2024/6/5/",
"relativeUrl": "2024/6/5/",
"thumbUrl": "http://10.130.1.11/fileupload/common/2024/6/5/724440db-d1af-48dc-a67a-5e124e78b566_thb.jpeg"
},
"success": true
}