推荐一个思路非常简单又很实用的文件上传下载方式,代码十分简练,可以开箱即用,下面是使用到的一些工具类和业务代码;
1.文件上传实现
判断文件类型的工具类,一些使用到的实体类我会凡在文末,需要可以的自取
public static int fileType(String fileName) {
if (fileName == null) {
return 0;
} else {
// 获取文件后缀名并转化为写,用于后续比较
String fileType = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
// 创建图片类型数组
String img[] = {"bmp", "jpg", "jpeg", "png", "tiff", "gif", "pcx", "tga", "exif", "fpx", "svg", "psd",
"cdr", "pcd", "dxf", "ufo", "eps", "ai", "raw", "wmf"};
for (int i = 0; i < img.length; i++) {
if (img[i].equals(fileType)) {
return 1;
}
}
// 创建文档类型数组
String document[] = {"txt", "doc", "docx", "xls", "htm", "html", "jsp", "rtf", "wpd", "pdf", "ppt"};
for (int i = 0; i < document.length; i++) {
if (document[i].equals(fileType)) {
return 2;
}
}
// 创建视频类型数组
String video[] = {"mp4", "avi", "mov", "wmv", "asf", "navi", "3 gp", "mkv", "f4v", "rmvb", "webm"};
for (int i = 0; i < video.length; i++) {
if (video[i].equals(fileType)) {
return 3;
}
}
// 创建音乐类型数组
String music[] = {"mp3", "wma", "wav", "mod", "ra", "cd", "md", "asf", "aac", "vqf", "ape", "mid", "ogg",
"m4a", "vqf"};
for (int i = 0; i < music.length; i++) {
if (music[i].equals(fileType)) {
return 4;
}
}
}
return 0;
}
1.2 Controller层代码
部分小伙伴因为是springboot项目且版本大于2.3,@Valid注解报错找不到,需要手动添加依赖,如果对“优雅的Java参数校验”感兴趣的伙伴可以见我另一篇文章优雅的参数校验@Validated 实战 + 统一异常处理返回前端json 最全解析
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
@Api(tags = "附件管理")
@RestController
@RequestMapping("/attach")
public class AttachmentController {
@Autowired
private AttachmentService attachmentService;
@PostMapping("/upload")
@ApiOperation(value = "上传文件")
public ResponseResult upload(@RequestPart("file") MultipartFile file,
@RequestParam(value = "bussinessId") @Valid @NotBlank(message = "业务id不能为空") String bussinessId) throws Exception {
return attachmentService.upload(file, bussinessId);
}
1.3 Service层业务代码
接口类
import com.yuncheng.entity.ResponseResult;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
public interface AttachmentService {
ResponseResult upload(MultipartFile file, String bussinessId) throws Exception;
}
实现类,注释很清晰了
import com.yuncheng.entity.ResponseResult;
import com.yuncheng.entity.enums.AppHttpCodeEnum;
import com.yuncheng.pc.entity.UploadFile;
import com.yuncheng.pc.mapper.UploadFileMapper;
import com.yuncheng.pc.service.AttachmentService;
import com.yuncheng.utils.Tools;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class AttachmentServiceImpl implements AttachmentService {
private static final String LOCAL_HOST = "http://10.11.0.104:8866/";
@Autowired
private UploadFileMapper uploadFileMapper;
@Override
@Transactional
public ResponseResult upload(MultipartFile multipartFile, String bussinessId) throws Exception {
if (multipartFile.isEmpty()) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID,"上传文件为空");
}
UploadFile uploadFile = new UploadFile();
// 保存原文件名称,文件列表展示需要用到
uploadFile.setFileName(multipartFile.getOriginalFilename());
// 生成系统文件名称,不可重复,防止同名文件上传覆盖问题
String name = RandomStringUtils.randomAlphanumeric(32) + multipartFile.getOriginalFilename().substring(multipartFile.getOriginalFilename().lastIndexOf(".")).toLowerCase();
uploadFile.setName(name.substring(0, name.lastIndexOf(".")));
uploadFile.setFileSuffix(multipartFile.getOriginalFilename().substring(multipartFile.getOriginalFilename().lastIndexOf(".")).toLowerCase());
// 判断文件类型
int fileType = Tools.fileType(name);
uploadFile.setType(fileType);
uploadFile.setDomain(LOCAL_HOST + "resource/files/");
//这种方式比类型强转效率更高
String pathExt = System.currentTimeMillis()+"";
uploadFile.setPath(LOCAL_HOST + "resource/files/" + pathExt);
uploadFile.setPathExt(pathExt);
// 获取文件大小
uploadFile.setSize((int)multipartFile.getSize());
uploadFile.setBussinessId(bussinessId);
uploadFile.setStatus(1);
uploadFile.setCreateTime(new Date());
uploadFile.setId(Tools.getCode32());
uploadFileMapper.insert(uploadFile);
// 将文件保存到本目录/resources/files/下
// DateUtil.today()得到得是当天日期如:20230715,这个会在/resources/files/下再以日期生成一层目录
File newFile = new File("./resources/files/"+ pathExt + "/" + name);
// 保证这个文件的父文件夹必须要存在
if (!newFile.getParentFile().exists()) {
newFile.getParentFile().mkdirs();
}
newFile.createNewFile();
// 将文件内容写入到这个文件中
InputStream is = multipartFile.getInputStream();
FileOutputStream fos = new FileOutputStream(newFile);
try {
int len;
byte[] buf = new byte[1024];
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
}
} finally {
// 关流顺序,先打开的后关闭
fos.close();
is.close();
}
// 返回文件信息给前端
Map resultMap = new HashMap();
resultMap.put("id", uploadFile.getId());
resultMap.put("path", uploadFile.getPath());
return ResponseResult.okResult(resultMap);
}
}
1.4 Mapper层代码
import com.yuncheng.pc.entity.UploadFile;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface UploadFileMapper {
void insert(@Param("uploadFile") UploadFile uploadFile);
}
对应sql语句
<insert id="insert">
insert into "SYSDBA"."ATTACHMENT"("ID", "CREATE_BY", "CREATE_TIME", "STATUS", "UPDATE_BY",
"UPDATE_TIME", "NAME", "FILE_NAME", "FILE_SUFFIX", "TYPE",
"DOMAIN", "PATH", "PATH_EXT", "SIZE", "BUSSINESS_ID")
VALUES(#{uploadFile.id},#{uploadFile.createBy},#{uploadFile.createTime},#{uploadFile.status},
#{uploadFile.updateBy},#{uploadFile.updateTime},#{uploadFile.name},#{uploadFile.fileName},
#{uploadFile.fileSuffix},#{uploadFile.type},#{uploadFile.domain},#{uploadFile.path},
#{uploadFile.pathExt},#{uploadFile.size},#{uploadFile.bussinessId})
</insert>
1.5 测试功能
至此文件上传代码就写好了,我们去postman测试一下,记住选post类型且文件类型要选对
点击发送即上传成功
2 文件下载代码
2.1 controller层代码
@GetMapping("/download")
@ApiOperation(value = "下载文件")
public void download(HttpServletResponse response,
@Valid @NotBlank(message = "id不能为空") String id) throws Exception {
attachmentService.download(response, id);
}
2.2 Service层代码
接口
ResponseResult download(HttpServletResponse response, String id) throws Exception;
实现类
@Override
public ResponseResult download(HttpServletResponse response, String id) throws Exception {
List<UploadFile> uploadFiles = uploadFileMapper.selectOne(id, null, 1);
if (CollectionUtils.isEmpty(uploadFiles)) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "查询文件为空");
}
// 去./resources/files/目录下取出文件
File downloadFile = new File("./resources/files/" +uploadFiles.get(0).getPathExt() + "/" + uploadFiles.get(0).getName() + uploadFiles.get(0).getFileSuffix());
if (!downloadFile.exists() || downloadFile.length() == 0) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "查询文件为空");
}
InputStream is = null;
OutputStream os = null;
try {
// 判断是否是图片,如果是图片加上 response.setContentType("image/jpeg"),这样就可以直接在浏览器打开而不是下载
if (uploadFiles.get(0).getType() == 1) {
response.setContentType("image/jpeg");
}
response.addHeader("Content-Length", "" + downloadFile.length());
is = new FileInputStream(downloadFile);
os = response.getOutputStream();
IOUtils.copy(is, os);
} catch (Exception e) {
log.error("下载图片发生异常", e);
} finally {
try {
if (os != null) {
os.flush();
os.close();
}
if (is != null) {
is.close();
}
} catch (IOException e) {
log.error("关闭流发生异常", e);
}
}
return ResponseResult.okResult("下载成功");
}
2.3 Mapper层代码
List<UploadFile> selectOne(@Param("id") String id, @Param("bussinessId") String bussinessId, @Param("status") Integer status);
对应SQL语句
<select id="selectOne" resultType="com.yuncheng.pc.entity.UploadFile">
select *
from "SYSDBA"."ATTACHMENT"
where status = #{status}
<if test="id != null and id != ''">
and id = #{id}
</if>
<if test="bussinessId != null and bussinessId != ''">
and bussiness_id = #{bussinessId}
</if>
</select>
2.4 测试功能
下面是一些使用到的实体类和建表语句,有需要的小伙伴可以自取,如果觉得文章对您有用的话记得点赞 收藏 关注哦!!!,主页全是实用文章待你来取!
文件上传实体类
import lombok.Data;
import java.util.Date;
@Data
public class UploadFile {
private String id;
//文件在系统中的名称
private String name;
//文件名称
private String fileName;
//文件后缀
private String fileSuffix;
//文件类型
private Integer type;
//主目录
private String domain;
//完整目录
private String path;
//扩展目录
private String pathExt;
//文件大小
private Integer size;
//关联的业务id
private String bussinessId;
//逻辑删除状态
private Integer status;
private Date createTime;
private String createBy;
private Date updateTime;
private String updateBy;
}
统一结果返回bean
import com.yuncheng.entity.enums.AppHttpCodeEnum;
import lombok.Data;
import java.io.Serializable;
/**
* 通用的结果返回类
* @param <T>
*/
@Data
public class ResponseResult<T> implements Serializable {
private String token;
private Integer code;
private String message;
private T data;
private String status;
public ResponseResult() {
this.code = 200;
}
public ResponseResult(Integer code, T data) {
this.code = code;
this.data = data;
}
public ResponseResult(Integer code, String msg, T data) {
this.code = code;
this.message = msg;
this.data = data;
}
public ResponseResult(Integer code, String msg) {
this.code = code;
this.message = msg;
}
public static ResponseResult okResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.ok(code, null, msg);
}
public static ResponseResult okResult(int code, Object data,String msg) {
ResponseResult result = new ResponseResult();
return result.ok(code, data, msg);
}
public static ResponseResult okResult(Object data) {
ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getMessage());
if(data!=null) {
result.setData(data);
}
return result;
}
public static ResponseResult errorResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.error(code, msg);
}
public static ResponseResult errorResult(AppHttpCodeEnum enums){
return setAppHttpCodeEnum(enums,enums.getMessage());
}
public static ResponseResult errorResult(AppHttpCodeEnum enums, String message){
return setAppHttpCodeEnum(enums,message);
}
public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
return okResult(enums.getCode(),enums.getMessage());
}
private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String message){
return okResult(enums.getCode(),message);
}
public ResponseResult<?> error(Integer code, String msg) {
this.code = code;
this.message = msg;
return this;
}
public ResponseResult<?> ok(Integer code, T data) {
this.code = code;
this.data = data;
return this;
}
public ResponseResult<?> ok(Integer code, T data, String msg) {
this.code = code;
this.data = data;
this.message = msg;
return this;
}
public ResponseResult<?> ok(T data) {
this.data = data;
return this;
}
}
状态码枚举类
package com.yuncheng.entity.enums;
public enum AppHttpCodeEnum {
// 成功段固定为200
SUCCESS(200,"操作成功"),
// 登录段1~50
NEED_LOGIN(1,"需要登录后操作"),
LOGIN_PASSWORD_ERROR(2,"密码错误"),
// TOKEN50~100
TOKEN_INVALID(50,"无效的TOKEN"),
TOKEN_EXPIRE(51,"TOKEN已过期"),
TOKEN_REQUIRE(52,"TOKEN是必须的"),
// SIGN验签 100~120
SIGN_INVALID(100,"无效的SIGN"),
SIG_TIMEOUT(101,"SIGN已过期"),
// 参数错误 500~1000
PARAM_REQUIRE(500,"缺少参数"),
PARAM_INVALID(501,"无效参数"),
PARAM_IMAGE_FORMAT_ERROR(502,"图片格式有误"),
SERVER_ERROR(503,"服务器内部错误"),
// 数据错误 1000~2000
DATA_EXIST(1000,"数据已经存在"),
AP_USER_DATA_NOT_EXIST(1001,"ApUser数据不存在"),
DATA_NOT_EXIST(1002,"数据不存在"),
DATA_DUPLICATE(1003,"数据重复"),
OPERATION_FAILED(1004,"操作失败"),
// 数据错误 3000~3500
NO_OPERATOR_AUTH(3000,"无权限操作"),
NEED_ADMIND(3001,"需要管理员权限");
int code;
String message;
AppHttpCodeEnum(int code, String errorMessage){
this.code = code;
this.message = errorMessage;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
建表语句
CREATE TABLE "SYSDBA"."ATTACHMENT"
(
"ID" VARCHAR2(64) NOT NULL,
"CREATE_BY" VARCHAR(64),
"CREATE_TIME" TIMESTAMP(0),
"STATUS" INTEGER,
"UPDATE_BY" VARCHAR(64),
"UPDATE_TIME" TIMESTAMP(0),
"NAME" VARCHAR(100),
"FILE_NAME" VARCHAR(100),
"FILE_SUFFIX" VARCHAR(50),
"TYPE" INTEGER,
"DOMAIN" CHARACTER(100),
"PATH" VARCHAR(100),
"PATH_EXT" VARCHAR(50),
"SIZE" INTEGER,
"BUSSINESS_ID" VARCHAR(100) NOT NULL) STORAGE(ON "MAIN", CLUSTERBTR) ;
COMMENT ON TABLE "SYSDBA"."ATTACHMENT" IS '附件表';
COMMENT ON COLUMN "SYSDBA"."ATTACHMENT"."BUSSINESS_ID" IS '业务id';
COMMENT ON COLUMN "SYSDBA"."ATTACHMENT"."CREATE_BY" IS '创建人';
COMMENT ON COLUMN "SYSDBA"."ATTACHMENT"."CREATE_TIME" IS '创建日期';
COMMENT ON COLUMN "SYSDBA"."ATTACHMENT"."DOMAIN" IS '主目录';
COMMENT ON COLUMN "SYSDBA"."ATTACHMENT"."FILE_NAME" IS '原文件名';
COMMENT ON COLUMN "SYSDBA"."ATTACHMENT"."FILE_SUFFIX" IS '文件后缀';
COMMENT ON COLUMN "SYSDBA"."ATTACHMENT"."ID" IS '业务id';
COMMENT ON COLUMN "SYSDBA"."ATTACHMENT"."NAME" IS '系统文件名';
COMMENT ON COLUMN "SYSDBA"."ATTACHMENT"."PATH" IS '路径';
COMMENT ON COLUMN "SYSDBA"."ATTACHMENT"."PATH_EXT" IS '扩展目录';
COMMENT ON COLUMN "SYSDBA"."ATTACHMENT"."SIZE" IS '文件大小';
COMMENT ON COLUMN "SYSDBA"."ATTACHMENT"."STATUS" IS '删除标志';
COMMENT ON COLUMN "SYSDBA"."ATTACHMENT"."TYPE" IS '类型';
COMMENT ON COLUMN "SYSDBA"."ATTACHMENT"."UPDATE_BY" IS '更新人';
COMMENT ON COLUMN "SYSDBA"."ATTACHMENT"."UPDATE_TIME" IS '修改时间';