笔者最近遇到了一个需求,需要搭建一个文件服务器,用于上传文件(.txt格式等)和图片(.jpg格式等)。
下面演示怎么上传txt文件和上传头像(.jpg),如有需要可自行修改后缀名进行拓展。
准备工作:
配置文件以及配置类:主要配置不同类型的文件路径,比如上传头像,可以放到 avatar目录,上传不同类型的文件方便区分,并获取其指定的上传路径。
配置文件application.yml:
# 项目相关配置
file-service:
# 文件路径
profile: D:/profile/
这里我配置了默认文件路径在D:/profile/,这里要注意:自定义配置文件属性规范名称应为kebab-case(’ - '分隔),小写字母数字字符,并且必须以字母开头。也就是说 如果写 fileService 或 FileService都是错的。
配置类WebConfig.java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 读取项目相关配置
*
* @author 健康搬砖人
*/
@Component
@ConfigurationProperties(prefix = "file-service")
public class WebConfig {
/**
* 上传路径
*/
private static String profile;
public static String getProfile() {
return profile;
}
public void setProfile(String profile) {
WebConfig.profile = profile;
}
// 获取上传头像路径
public static String getAvatarPath() {
return profile + "avatar/";
}
// 获取下载路径
public static String getDownloadPath() {
return profile + "download/";
}
// 获取上传路径
public static String getUploadPath() {
return profile + "upload/";
}
}
依赖文件:
Pom.xml需要用到如下的依赖:
<!--常用工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!--io常用工具类 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
org.apache.commons这个是常用工具类,用于文件分类,后面我会写一个DateUtils,目的用于生成上传或下载的日期文件夹,比如上传个头像,目录可能是这样的
D:/profile/avatar/2019/05/20/d025ba6f937f59999a021989a12a1aab.jpg
其中的2019/05/20就是根据你上传或下载的日期而定。
日期处理类:
import org.apache.commons.lang3.time.DateFormatUtils;
import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 时间工具类
*
* @author 健康搬砖人
*/
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
public static String YYYY = "yyyy";
public static String YYYY_MM = "yyyy-MM";
public static String YYYY_MM_DD = "yyyy-MM-dd";
public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
private static String[] parsePatterns = {
"yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
"yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
"yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
/**
* 获取当前Date型日期
*
* @return Date() 当前日期
*/
public static Date getNowDate() {
return new Date();
}
/**
* 获取当前日期, 默认格式为yyyy-MM-dd
*
* @return String
*/
public static String getDate() {
return dateTimeNow(YYYY_MM_DD);
}
public static final String getTime() {
return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
}
public static final String dateTimeNow() {
return dateTimeNow(YYYYMMDDHHMMSS);
}
public static final String dateTimeNow(final String format) {
return parseDateToStr(format, new Date());
}
public static final String dateTime(final Date date) {
return parseDateToStr(YYYY_MM_DD, date);
}
public static final String parseDateToStr(final String format, final Date date) {
return new SimpleDateFormat(format).format(date);
}
public static final Date dateTime(final String format, final String ts) {
try {
return new SimpleDateFormat(format).parse(ts);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
/**
* 日期路径 即年/月/日 如2018/08/08
*/
public static final String datePath() {
Date now = new Date();
return DateFormatUtils.format(now, "yyyy/MM/dd");
}
/**
* 日期路径 即年/月/日 如20180808
*/
public static final String dateTime() {
Date now = new Date();
return DateFormatUtils.format(now, "yyyyMMdd");
}
/**
* 日期型字符串转化为日期 格式
*/
public static Date parseDate(Object str) {
if (str == null) {
return null;
}
try {
return parseDate(str.toString(), parsePatterns);
} catch (ParseException e) {
return null;
}
}
/**
* 获取服务器启动时间
*/
public static Date getServerStartDate() {
long time = ManagementFactory.getRuntimeMXBean().getStartTime();
return new Date(time);
}
/**
* 计算两个时间差
*/
public static String getDatePoor(Date endDate, Date nowDate) {
long nd = 1000 * 24 * 60 * 60;
long nh = 1000 * 60 * 60;
long nm = 1000 * 60;
// long ns = 1000;
// 获得两个时间的毫秒时间差异
long diff = endDate.getTime() - nowDate.getTime();
// 计算差多少天
long day = diff / nd;
// 计算差多少小时
long hour = diff % nd / nh;
// 计算差多少分钟
long min = diff % nd % nh / nm;
// 计算差多少秒//输出结果
// long sec = diff % nd % nh % nm / ns;
return day + "天" + hour + "小时" + min + "分钟";
}
}
MD5加密类:用于对文件名进行hash加密,防止上传的文件名相同。
package com.warren.springboot.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.MessageDigest;
/**
* Md5加密方法
*
* @author 健康搬砖人
*/
public class Md5Utils {
private static final Logger log = LoggerFactory.getLogger(Md5Utils.class);
private static byte[] md5(String s) {
MessageDigest algorithm;
try {
algorithm = MessageDigest.getInstance("MD5");
algorithm.reset();
algorithm.update(s.getBytes("UTF-8"));
byte[] messageDigest = algorithm.digest();
return messageDigest;
} catch (Exception e) {
log.error("MD5 Error...", e);
}
return null;
}
private static final String toHex(byte hash[]) {
if (hash == null) {
return null;
}
StringBuffer buf = new StringBuffer(hash.length * 2);
int i;
for (i = 0; i < hash.length; i++) {
if ((hash[i] & 0xff) < 0x10) {
buf.append("0");
}
buf.append(Long.toString(hash[i] & 0xff, 16));
}
return buf.toString();
}
public static String hash(String s) {
try {
return new String(toHex(md5(s)).getBytes("UTF-8"), "UTF-8");
} catch (Exception e) {
log.error("not supported charset...{}", e);
return s;
}
}
}
文件上传工具类:
import com.warren.springboot.config.WebConfig; //这个是导入上面的WebConfig.java配置文件
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
/**
* 文件上传工具类
*
* @author 健康搬砖人
*/
public class FileUploadUtils {
/**
* 默认大小 50M
*/
public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
/**
* 默认的文件名最大长度 100
*/
public static final int DEFAULT_FILE_NAME_LENGTH = 100;
/**
* 默认上传的地址
*/
private static String defaultBaseDir = WebConfig.getProfile();
/**
* 默认文件类型jpg
*/
public static final String IMAGE_JPG_EXTENSION = ".jpg";
private static int counter = 0;
public static void setDefaultBaseDir(String defaultBaseDir) {
FileUploadUtils.defaultBaseDir = defaultBaseDir;
}
public static String getDefaultBaseDir() {
return defaultBaseDir;
}
/**
* 根据文件路径上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @return 文件名称
* @throws IOException
*/
public static final String uploadPicture(String baseDir, MultipartFile file) throws IOException {
try {
return upload(baseDir, file, FileUploadUtils.IMAGE_JPG_EXTENSION);
} catch (Exception e) {
throw new IOException(e.getMessage(), e);
}
}
/**
* 根据文件路径上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @return 文件名称
* @throws IOException
*/
public static final String uploadText(String baseDir, MultipartFile file) throws IOException {
try {
return upload(baseDir, file, ".txt");
} catch (Exception e) {
throw new IOException(e.getMessage(), e);
}
}
/**
* 文件上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @param extension 上传文件类型
* @return 返回上传成功的文件名
* @throws IOException 比如读写文件出错时
*/
public static final String upload(String baseDir, MultipartFile file, String extension) throws IOException{
int fileNamelength = file.getOriginalFilename().length();
if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) {
// 此处可进行异常处理throws
System.out.println("文件名长度超出限定长度");
}
assertAllowed(file);
String fileName = extractFilename(file, extension);
File desc = getAbsoluteFile(baseDir, baseDir + fileName);
file.transferTo(desc);
return fileName;
}
public static final String extractFilename(MultipartFile file, String extension) {
String filename = file.getOriginalFilename();
filename = DateUtils.datePath() + "/" + encodingFilename(filename) + extension;
return filename;
}
private static final File getAbsoluteFile(String uploadDir, String filename) throws IOException {
File desc = new File(File.separator + filename);
if (!desc.getParentFile().exists()) {
desc.getParentFile().mkdirs();
}
if (!desc.exists()) {
desc.createNewFile();
}
return desc;
}
/**
* 编码文件名
*/
private static final String encodingFilename(String filename) {
filename = filename.replace("_", " ");
filename = Md5Utils.hash(filename + System.nanoTime() + counter++);
return filename;
}
/**
* 文件大小校验
*
* @param file 上传的文件
*/
public static final void assertAllowed(MultipartFile file) {//throws FileSizeLimitExceededException {
long size = file.getSize();
if (size > DEFAULT_MAX_SIZE) {
// 此处可进行异常处理throws
System.out.println("文件大小超出最大限定大小");
}
}
}
准备工作就到这里结束了,下面直接就放大招了。
项目实战
一、文件上传
/**
* 保存文件
*
* @param file 文件
* @return 结果
*/
@PostMapping("/updateFile")
@ResponseBody
public Object updateFile(@RequestParam("file") MultipartFile file) {
try {
if (!file.isEmpty()) {
String fileName = FileUploadUtils.uploadText(WebConfig.getUploadPath(), file);
System.out.println("fileName:" + fileName);
// TODO: 此处可以根据项目需求的业务进行操作,例如将fileName保存到数据库
}
return "success";
} catch (Exception e) {
return "error";
}
}
二、头像/图片上传
图片上传和文件上传类似。
/**
* 保存头像
*
* @param file 头像文件
* @return 结果
*/
@PostMapping("/updateAvatar")
@ResponseBody
public Object updateAvatar(@RequestParam("avatarfile") MultipartFile file) {
try {
if (!file.isEmpty()) {
String avatar = FileUploadUtils.uploadPicture(WebConfig.getAvatarPath(), file);
System.out.println("avatar:" + avatar);
// TODO: 此处可以根据项目需求的业务进行操作,例如此处操作的是头像,可以将头像url保存到数据库,用户登录后,获取相应的url获取头像图片。
}
return "success";
} catch (Exception e) {
return "error";
}
}
三、文件下载
这里文件下载的业务分为几种。
①直接查看图片,类似业务:查看图片/查看头像
/**
* 查看文件
*
* @param url 图片url
* @param response 请求响应
*/
@RequestMapping(value = "/noLogin/readImageFile",method =RequestMethod.GET)
@ResponseBody
public void getUrlFile(String url, HttpServletResponse response) {
// 这里的url,我为了测试,直接就写静态的。
url = "D:/profile/avatar/2019/05/20/d025ba6f937f59999a021989a12a1aab.jpg";
File file = new File(url);
// 后缀名
String suffixName = url.substring(url.lastIndexOf("."));
//判断文件是否存在如果不存在就返回默认图片或者进行异常处理
if (!(file.exists() && file.canRead())) {
// file = new File("xxx/xxx.jpg");
System.out.println("文件不存在");
}
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
byte[] data = new byte[(int) file.length()];
inputStream.close();
response.setContentType("image/png;charset=utf-8");
OutputStream stream = response.getOutputStream();
stream.write(data);
stream.flush();
stream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
效果如下:
②将文件内容(.txt)封装到一个String里
@RequestMapping(value = "/noLogin/readFileToString",method = RequestMethod.GET)
@ResponseBody
public String getUrlFile(String url, HttpServletRequest request, HttpServletResponse response) {
// url = "D:/profile/upload/2019/05/20/01017f12b88d9f4e79dc38060e3883da.txt";
// 通过url创建文件
File file = new File(url);
// 后缀名
String suffixName = url.substring(url.lastIndexOf("."));
//判断文件是否存在如果不存在就进行异常处理
if (!(file.exists() && file.canRead())) {
System.out.println("文件不存在");
}
FileInputStream inputStream = null;
String content = "";
try {
inputStream = new FileInputStream(file);
inputStream.close();
// 使用FileUtils将File内容以UTF-8的编码写到String里
content = FileUtils.readFileToString(file, "UTF-8");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return content;
}
③文件下载
/**
* 下载文件
*
* @param url 文件url
* @param response 请求响应
*/
@RequestMapping(value = "/noLogin/downloadFile",method =RequestMethod.GET)
@ResponseBody
public void getUrlDownload(String url, HttpServletResponse response) {
url = "D:/profile/avatar/2019/05/20/d025ba6f937f59999a021989a12a1aab.jpg";
File file = new File(url);
// 后缀名
String suffixName = url.substring(url.lastIndexOf("."));
//判断文件是否存在如果不存在就进行异常处理
if (!(file.exists() && file.canRead())) {
System.out.println("文件不存在");
}
FileInputStream inputStream = null;
BufferedInputStream bufferedInputStream = null;
try {
inputStream = new FileInputStream(file);
byte[] data = new byte[(int) file.length()];
int length = inputStream.read(data);
inputStream.close();
response.setContentType("application/force-download");
//通过设置头信息给文件命名,也即是,在前端,文件流被接受完还原成原文件的时候会以你传递的文件名来命名
response.addHeader("Content-Disposition",String.format("attachment; filename=\"%s\"", file.getName()));
OutputStream stream = response.getOutputStream();
stream.write(data);
stream.flush();
stream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
效果如下: