对于该篇文章涉及的知识点可看我之前的文章:
java知识点细节:java框架零基础从入门到精通的学习路线(超全)
springboot知识点:springboot从入门到精通(全)
细节知识点具体有如下:@GetMapping、@PostMapping 和 @RequestMapping详细区别附实战代码(全)
IO流以及缓冲区输入输出基础知识:
以上知识点查漏补缺或者在下面正文中遇到了可对应进行学习即可
文件的上传下载等借口,使用最多的两个类File以及MultipartFile类,先看源代码讲解
关于File类可看我之前的文章:java关于File类源码的详细分析 附代码(全)
上传多文件主要用到这个接口:MultipartFile file
单文件为file,多文件上传即存到数组,通过遍历一个个取出即可
源码:
public interface MultipartFile extends InputStreamSource {
String getName();
@Nullable
String getOriginalFilename();
@Nullable
String getContentType();
boolean isEmpty();
long getSize();
byte[] getBytes() throws IOException;
InputStream getInputStream() throws IOException;
default Resource getResource() {
return new MultipartFileResource(this);
}
void transferTo(File dest) throws IOException, IllegalStateException;
default void transferTo(Path dest) throws IOException, IllegalStateException {
FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest));
}
}
该接口有多个方法属性
大致如下:
参数 | 描述 |
---|---|
getName() | 文件格式 |
getOriginalFilename() | 文件名 |
getContentType | 文件类型 |
isEmpty() | 文件是否为空 |
getSize() | 文件大小 |
上传一张照片的时候,输出的结果大致如下:
文件中的一些其他参数,比如上传时间、文件后缀等可自个优化
获取文件后缀的定义可通过substring截取
函数如下:
fileName.substring(fileName.lastIndexOf("."));
所谓的本地接口,也就是通过本地进行上传下载删除更改文件目录显示等接口
加深各个函数之间的运用之后,在对应进行实战开发
函数名如下:
@RestController
@RequestMapping("file")
@Slf4j
public class filecontroller2 {
对于slf4j的注解可看我这两文章:(主要是打印日志的文件)
上传的接口赋值一个文件名(埋下伏笔,已经写死只能从这个文件进行上传,后续会对应进行优化):
@Value("${file.upload.url}")
private String uploadFilePath;
注解的引入在配置文件中(application.properties):file.upload.url=E:/upload
之后可以通过uuid的随机赋值文件名
//路径 + UUID(文件名)
String realPath = uploadFilePath + "/" + UUID.randomUUID().toString().replaceAll("-", "");
File dest = new File(realPath);
//判断是否存在这个目录,如果不存在就在当前路径下创建
if (!dest.exists() && !dest.isDirectory()) {
//创建多级文件夹
dest.getParentFile().mkdirs();
}
也可以使用当前的filename进行命名:
File dest = new File(uploadFilePath +'/'+ fileName);
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
完整示例代码如下:
以下为多文件上传,如果改动为单文件,只需要将其数组以及遍历去除即可
// http://127.0.0.1:8080/file/upload
// 填写files的参数,问号不用带
/*
* 上传多个文件
* */
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public String FileUpload(@RequestParam("files") MultipartFile[] files){
//终端以json格式传输,可以使用JSONObject这个类,将其json格式打在显示上
JSONObject object=new JSONObject();
//遍历各个文件
for(int i=0;i<files.length;i++){
String fileName = files[i].getOriginalFilename();
//判断是否存在这个目录,如果不存在就在当前路径下创建
File dest = new File(uploadFilePath +'/'+ fileName);
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
try {
//上传到服务器
files[i].transferTo(dest);
object.put("success","上传成功");
} catch (Exception e) {
object.put("fail","上传失败,重新上传");
}
}
return object.toString();
}
对应通过接口测试如下:
测试接口可通过PostMan、ApiPost等软件,可看我这篇文章的讲解:国货之光的API管理软件 - Apipost
通过对应的流进行下载,之后别忘记关闭流的传输
//http://127.0.0.1:8080/file/download?fileName=1.png
//@GetMapping("/download")
@RequestMapping(value = "/download", method = RequestMethod.GET)
public String fileDownLoad(HttpServletResponse response, @RequestParam("fileName") String fileName){
File file = new File(uploadFilePath +'/'+ fileName);
if(!file.exists()){
return "下载文件不存在";
}
response.reset();
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "multipart/form-data");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName );
try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());
) {
byte[] buff = new byte[1024];
int len = 0;
while ((len = bis.read(buff)) != -1) {
bos.write(buff, 0, len);
bos.flush();
}
} catch (IOException e) {
return "下载失败";
}
return "下载成功";
}
处理中断输入输出流的时候的异常,加入finally比较保守
改进代码如下:
//http://127.0.0.1:8080/file/download?fileName=1.png
//@GetMapping("/download")
@RequestMapping(value = "/download", method = RequestMethod.GET)
public String fileDownLoad(HttpServletResponse response, @RequestParam("fileName") String fileName){
File file = new File(uploadFilePath +'/'+ fileName);
if(!file.exists()){
return "下载文件不存在";
}
response.reset();
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "multipart/form-data");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName );
FileInputStream fis = null;
BufferedInputStream bis = null;
try {
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());
byte[] buff = new byte[1024];
int len = 0;
while ((len = bis.read(buff)) != -1) {
bos.write(buff, 0, len);
bos.flush();
}
return "下载成功";
} catch (IOException e) {
e.printStackTrace();
return "下载失败";
}finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
return "输入流异常";
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
return "文件流异常";
}
}
}
}
可以使用FileSystemUtils的类对应进行删除
// http://127.0.0.1:8080/file3/delete path是路径
// @PostMapping ("/delete")
@RequestMapping(value = "/delete", method = RequestMethod.POST)
public Boolean DeletePathFile(@RequestParam("path")String path){
return FileSystemUtils.deleteRecursively(new File(path));
}
通过api接口测试中显示true,这是因为这个类本身返回值就是Boolean值,如果删除成功返回true
该demo在本地中只能显示该路径下(已经写死)的所有目录文件
// http://127.0.0.1:8080/file/getAllDirs
/*
* 获取当前路径下的所有目录
* */
@RequestMapping(value = "/getAllDirs", method = RequestMethod.POST)
public Object getAllDirs(String filepath ) {
Map<String, Object> map = new HashMap<>();
try {
// 获取目录 主要是通过递归
List<String> dirList = getAllDir(uploadFilePath,true);
map.put("data", dirList);
map.put("code", 200);
map.put("查询目录", uploadFilePath);
map.put("message", "查询成功");
} catch (Exception e) {
e.printStackTrace();
map.put("查询目录", "无此目录");
map.put("message", "查询失败");
}
return map;
}
该递归目录的算法如下:
// 获取当前路径下的所有文件/文件夹
public List<String> getAllDir(String directoryPath, boolean isDirectory) {
List<String> list = new ArrayList<String>();
// 将其String转换为file文件
File file = new File(directoryPath);
// 当前如果目录不存在,或者路径是一个文件,直接返回空列表
// 此处的路径本身已经写死了,变成一个目录,所以不用在判断是不是文件,或者路径是不是存在
// 将其文件存储在文件中放置在数组内部
// 将其每个文件
File[] files = file.listFiles();
for (int i = 0;i< files.length; i++) {
if (files[i].isDirectory()) {
if (isDirectory) {
// 一个个对应添加到其list中,也就是绝对路径
list.add(files[i].getAbsolutePath());
}
// 递归嵌套遍历类似谷粒商城的三级分组,或者lc 中的递归算法
list.addAll(getAllDir(files[i].getAbsolutePath(), isDirectory));
}
}
return list;
}
// http://127.0.0.1:8080/file/getAllFiles
/*
* 获取当前路径下的所有文件
* */
@RequestMapping(value = "/getAllFiles", method = RequestMethod.GET)
public Object getAllFiles() {
Map<String, Object> dataMap = new HashMap<>();
Map<String, Object> listMap = new HashMap<>();
try {
List<Object> list = getAllFilesmethod(uploadFilePath);
listMap.put("list",list);
dataMap.put("data", listMap);
dataMap.put("code", 0);
dataMap.put("message", "查询成功");
} catch (Exception e) {
e.printStackTrace();
dataMap.put("data", "");
dataMap.put("code", 500);
dataMap.put("message", "查询失败");
}
log.info(""+dataMap);
return dataMap;
}
获取所有文件的方法:
public List<Object> getAllFilesmethod(String directoryPath) {
List<Object> list = new ArrayList<Object>();
File file1 = new File(directoryPath);
File[] files = file1.listFiles();
for (File file : files) {
Map<String, String> map = new HashMap<>();
if (file.isFile()) {
map.put("fileName", file.getName());
map.put("filePath", file.getAbsolutePath());
list.add(map);
}
}
return list;
}
如果想显示文件的上传时间或者是 电脑内部文件的上传时间(通过fs自带的类可以获取)
上传时间的方法如下:new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()
显示文件大小可以通过MultipartFile类中的ile.getSize()获取文件大小
但是这种大小只有数字,转换为B、KB等规格
需要增加一种判断,具体如下:
public String transferfileSize(long fileLen) {
DecimalFormat df = new DecimalFormat("#.00");
DecimalFormat d = new DecimalFormat("#");
String fileSize ;
if (fileLength < 1024) {
fileSize = d.format((double) fileLen) + "B";
} else if (fileLength < 1048576) {
fileSize = df.format((double) fileLen/ 1024) + "KB";
} else if (fileLength < 1073741824) {
fileSize = df.format((double) fileLen/ 1048576) + "MB";
} else {
fileSize = df.format((double) fileLen/ 1073741824) + "GB";
}
return fileSize;
}
controller代码模块和上面大同小异,区别在于核心方法
此处post出核心模块
// 更改目录
// http://127.0.0.1:8080/file/FixFileName
// filePath为路径,newFileName为更改的名字
public String modifyFileName(String filePath, String newFileName) {
File file = new File(filePath);
// 判断原文件是否存在(防止文件名冲突)
if (!file.exists()) {
return null;
}
// 去除前后的空格
newFileName = newFileName.trim();
// 文件名为空,则返回null
if ("".equals(newFileName) || newFileName == null)
return null;
String newFilePath = null;
// 文件和文件夹直接更改名字,此处的demo为windows系统,如果要将其变换linux,需要增加判断
newFilePath = filePath.substring(0, filePath.lastIndexOf("\\")) + "\\" + newFileName;
try {
// 修改文件名,内部需要是文件类,所以将其转换
file.renameTo(new File(newFilePath));
} catch (Exception e) {
e.printStackTrace();
return null;
}
return newFilePath;
}
controller代码模块和上面大同小异,区别在于核心方法
此处post出核心模块
主要思想是,如果创建的这个目录或者文件存在的时候,对应在后面加上(i)
,通过遍历加上合适的i
// http://127.0.0.1:8080/file/makeDirs filePath创建绝对路径
/*是否存在这个目录,创建目录
* */
public static boolean ismakeDirs(String path) {
File file = new File(path);
int i = 1; // 定义全局变量,主要用来增加i
try {
// 如果文件夹不存在且没有这个文件 直接创建
// 这种情况比较简单
if (!file.exists() && !file.isDirectory()) {
file.mkdirs();
return true;
}else{
//获取最后一个分割符的后缀名
String name = path.substring(path.lastIndexOf("/")+1,path.length());
while(file.exists()) {
// 创建文件:上级目录 + 系统分割符 + 名字 + (i)
file = new File(file.getParent()+ File.separator+name+"("+i+")");
i++;
}
file.mkdirs();
return true;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
思路:将其文件通过s3,获取到上传链接以及下载链接,之后将对应的信息都放置在mongo数据库中存储(比如文件名、上传时间、上传链接、下载链接等),之后通过对应操作进行增删改查
代码:以下代码只是提供思路(部分代码有些耦合,没有定义多一个mapper类),对接aws s3代码没post出来
三个注意点规范:
定义一个接口类:
public interface FileUploadService {
以及接口实现类:
@Slf4j
@Service
public class FileUploadServiceImpl implements FileUploadService {
此处的上传可以通过任意位置,上传到字节流,在上传到服务器(上面的本地测试是写死某个路径)
ResultGeneralModel这个类,是自个封装的json格式返回结果(此处就不post出这个实体类)
类似的可以通过上面json或者定义一个map集合封装json格式仿照下
//todo 第一个接口为上传接口
// http://127.0.0.1:10000/file/upload
@RequestMapping(method = RequestMethod.POST, value = "file/upload")
public ResultGeneralModel<String> fileUpload(@RequestParam("file") MultipartFile file) {
LinkedList<String> list = fileUploadService.httpUpload(file);
String getUploadUrl = list.getFirst();
String getDownloadUrl = list.getLast();
mongoService.uploadmongo(file.getOriginalFilename(), file.getSize(), getUploadUrl, getDownloadUrl);
return ResultGeneralModel.newSuccess("成功上传");
}
httpUpload的核心方法如下:
@Override
public LinkedList<String> httpUpload(MultipartFile file) {
String getUploadUrl;
LinkedList<String> list = new LinkedList<>();
SdkFileInfo sdkFileInfo = new SdkFileInfo();
// 定义了一个sdkFileInfo的实体类,appid也可写死 也可通过form表单的传输引入
// 此处post出思路引导
int appid = sdkFileInfo.getAppid();
try {
list = uploadS3(appid, file ,sdkFileInfo);
getUploadUrl = list.getFirst();
if (null == getUploadUrl) {
return null;
}
logger.info("file upload success");
} catch (Exception e) {
logger.error("file error");
}
return list;
}
具体上传到服务器的uploadS3的函数如下:
通过将其文件变成字节流在传输到服务器,之后获取其相关信息返回
public LinkedList<String> uploadS3(int appId, MultipartFile file, SdkFileInfo sdkFileInfo) {
LinkedList<String> list = new LinkedList<>();
// 获取s3的相关信息
S3Info info = getS3Info(appId, file);
// 判断
list.add(info.getUpload_url());
logger.info(info.getUpload_url());
if (null == info) {
logger.error("get s3 info error, user info:" + sdkFileInfo);
return null;
}
byte[] bytes;
try {
// 转换为字节流
bytes = file.getBytes();
} catch (IOException e) {
logger.error("read file bytes fail, user info:" + sdkFileInfo, e);
return null;
}
// 之后通过putUploadFile的方法进行传输,此处对应s3服务器的对接文档
// 转换字节流对应传输,此方法没有post出来
if (!httpService.putUploadFile(info.getUpload_url(), bytes)) {
logger.error("upload file error, user info:" + sdkFileInfo);
return null;
}
list.add(info.getDownload_url());
logger.info(info.getDownload_url());
return list;
}
具体获取S3的相关信息,首先需要账号密码以及字段值的配对
private S3Info getS3Info(int appId, MultipartFile file) {
// 获取S3的相关信息(通过注解引入commonConfig 之后getS3Url获取url属性)
String url = commonConfig.getS3Url();
Map<String, String> params = new HashMap<>();
// 对应传输进入属性
params.put("product", "xxx");
params.put("expire", String.valueOf(appId));
// 获取其后缀,如果有后缀则添加进入,下载链接会有后缀,直接下载文件
String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")+1);
if(suffix != null){
params.put("suffix",suffix);
}
// 判断url的参数结果
// 通过注解引入httpService
// 此处的代码需参考s3,同样也不post出代码
String resultString = httpService.get(url, params);
String dataString = httpService.httpResultCheck(url, params, resultString);
return null != dataString ? JSON.parseObject(dataString, S3Info.class) : null;
}
对应的数据库那一块的处理,通过uploadmongo函数,该函数如下:
@Override
public void uploadmongo(String fileName, Long fileSize, String getUploadUrl, String getDownloadUrl){
SdkInfo sdkInfo = new SdkInfo();
sdkInfo.setCreateTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
sdkInfo.setFileSize(fileSize);
sdkInfo.setFileName(fileName);
sdkInfo.setOperatorName("码农研究僧");
sdkInfo.setUploadUrl(getUploadUrl);
sdkInfo.setDownloadUrl(getDownloadUrl);
SdkInfo insert = mongoTemplate.insert(sdkInfo);
if(String.valueOf(insert) != null){
logger.info(String.valueOf(insert));
}else{
logger.info("Failed to insert data");
}
}
思路:由于mongo的数据库以及存储了下载链接,通过查询mongo的数据库,之后对应的链接进行重定向即可下载
controller类:
// http://127.0.0.1:10000/file/download
@RequestMapping(value = "file/download" , method = RequestMethod.GET)
public void fileDownLoad(@RequestParam("_id") String id, HttpServletResponse response){
String getDownloadUrl = mongoService.findmongo(id);
try {
// 链接的重定向,使得直接条状下载
response.sendRedirect(getDownloadUrl);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
接口:public String findmongo(String id);
接口实现类:
@Override
public String findmongo(String id){
// 通过每一行的关键信息查找对应某一行
Query query = new Query(Criteria.where("id").is(id));
List<SdkInfo> configs = mongoTemplate.find(query, SdkInfo.class);
// 获取这一行的对应下载数据的链接
logger.info("文件下载链接:" + configs.get(0).getDownloadUrl());
return configs.get(0).getDownloadUrl();
}
思路:上传与删除一样,需要对服务器进行操作之后在对数据库进行操作
服务器的操作需要对接aws s3(省略)
删除数据库的代码模块(通过form表单 获取对应的某个字段来或者这一行数据,之后删除这一行数据即可)
@Override
public void deletemongo(String id) {
Query query = new Query(Criteria.where("_id").is(id));
mongoTemplate.remove(query, SdkInfo.class);
}
对应回显在前端是通过什么字段值,做好规范约束
前端的规范约束是这个:
*接口需要支持的参数:
page: Int // 当前页码
size: Int // 每页条数
*接口返回的字段规范如下:
{
code:0,
data:{
list:[
{
id: Int|String,
...
}
]
}
}
对应接口规范,代码为:
private final MongoService mongoService;
// http://127.0.0.1:10000/file/getAllFiles
@RequestMapping(value = "file/getAllFiles" , method = RequestMethod.GET)
public Object getAllFiles(@RequestParam("page") int page,
@RequestParam("size") int size){
Map<String, Object> dataMap = new HashMap<>();
Map<String, Object> listMap = new HashMap<>();
try {
List<Object> list = mongoService.showList();
listMap.put("list",list);
dataMap.put("data", listMap);
dataMap.put("code", 0);
dataMap.put("message", "查询成功");
} catch (Exception e) {
e.printStackTrace();
dataMap.put("data", "");
dataMap.put("code", 500);
dataMap.put("message", "查询失败");
}
logger.info(""+dataMap);
return dataMap;
}
书写接口类:
public List<Object> showList();
接口实现类:
// SdkInfo.class是我定义的一个类
@Override
public List<Object> showList() {
Query query = new Query();
List<SdkInfo> configs = mongoTemplate.find(query, SdkInfo.class);
List<Object> list = new ArrayList<Object>();
configs.forEach(config -> {
Map<String, String> map = new HashMap<>();
map.put("_id",config.getId());
map.put("文件名",config.getFileName());
map.put("文件大小", config.getFileSize());
map.put("创建时间", config.getCreateTime());
map.put("上传用户",config.getOperatorName());
map.put("下载链接",config.getDownloadUrl());
list.add(map);
});
return list;
}