前言
对于该篇文章涉及的知识点可看我之前的文章: java知识点细节:java框架零基础从入门到精通的学习路线(超全) springboot知识点:springboot从入门到精通(全)
细节知识点具体有如下:@GetMapping、@PostMapping 和 @RequestMapping详细区别附实战代码(全)
IO流以及缓冲区输入输出基础知识:
- java NIO从入门到精通(全)
- javaSE从入门到精通的二十万字总结(二)
以上知识点查漏补缺或者在下面正文中遇到了可对应进行学习即可
1. 知识点补充
文件的上传下载等借口,使用最多的两个类File以及MultipartFile类,先看源代码讲解
1.1 File类
关于File类可看我之前的文章:java关于File类源码的详细分析 附代码(全)
1.2 MultipartFile类
上传多文件主要用到这个接口: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("."));
2. 本地测试
所谓的本地接口,也就是通过本地进行上传下载删除更改文件目录显示等接口 加深各个函数之间的运用之后,在对应进行实战开发
2.1 上传文件
函数名如下:
@RestController
@RequestMapping("file")
@Slf4j
public class filecontroller2 {
对于slf4j的注解可看我这两文章:(主要是打印日志的文件)
- java常见log日志的使用方法详细解析
- 出现SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder“.的解决方法
上传的接口赋值一个文件名(埋下伏笔,已经写死只能从这个文件进行上传,后续会对应进行优化):
@Value("${file.upload.url}")
private String uploadFilePath;
注解的引入在配置文件中(application.properties):file.upload.url=E:/upload
之后可以通过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();
}
完整示例代码如下:
以下为多文件上传,如果改动为单文件,只需要将其数组以及遍历去除即可
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public String FileUpload(@RequestParam("files") MultipartFile[] files){
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
2.2 下载文件
通过对应的流进行下载,之后别忘记关闭流的传输
@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比较保守 改进代码如下:
@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 "文件流异常";
}
}
}
}
2.3 删除文件
可以使用FileSystemUtils的类对应进行删除
@RequestMapping(value = "/delete", method = RequestMethod.POST)
public Boolean DeletePathFile(@RequestParam("path")String path){
return FileSystemUtils.deleteRecursively(new File(path));
}
通过api接口测试中显示true,这是因为这个类本身返回值就是Boolean值,如果删除成功返回true
2.4 显示所有目录
该demo在本地中只能显示该路径下(已经写死)的所有目录文件
@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>();
File file = new File(directoryPath);
File[] files = file.listFiles();
for (int i = 0;i< files.length; i++) {
if (files[i].isDirectory()) {
if (isDirectory) {
list.add(files[i].getAbsolutePath());
}
list.addAll(getAllDir(files[i].getAbsolutePath(), isDirectory));
}
}
return list;
}
2.5 显示所有文件
@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;
}
2.6 更改 文件/目录 名
controller代码模块和上面大同小异,区别在于核心方法 此处post出核心模块
public String modifyFileName(String filePath, String newFileName) {
File file = new File(filePath);
if (!file.exists()) {
return null;
}
newFileName = newFileName.trim();
if ("".equals(newFileName) || newFileName == null)
return null;
String newFilePath = null;
newFilePath = filePath.substring(0, filePath.lastIndexOf("\\")) + "\\" + newFileName;
try {
file.renameTo(new File(newFilePath));
} catch (Exception e) {
e.printStackTrace();
return null;
}
return newFilePath;
}
2.7 创建 文件/目录
controller代码模块和上面大同小异,区别在于核心方法 此处post出核心模块
主要思想是,如果创建的这个目录或者文件存在的时候,对应在后面加上(i) ,通过遍历加上合适的i
public static boolean ismakeDirs(String path) {
File file = new File(path);
int i = 1;
try {
if (!file.exists() && !file.isDirectory()) {
file.mkdirs();
return true;
}else{
String name = path.substring(path.lastIndexOf("/")+1,path.length());
while(file.exists()) {
file = new File(file.getParent()+ File.separator+name+"("+i+")");
i++;
}
file.mkdirs();
return true;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
3. 实战开发
思路:将其文件通过s3,获取到上传链接以及下载链接,之后将对应的信息都放置在mongo数据库中存储(比如文件名、上传时间、上传链接、下载链接等),之后通过对应操作进行增删改查
代码:以下代码只是提供思路(部分代码有些耦合,没有定义多一个mapper类),对接aws s3代码没post出来
三个注意点规范:
- 使用云服务aws s3的文件传输(对应服务器的传输需看好规范文档)
对接服务器这块内容使用的是微服务项目的接口(各个项目独立分离,而且每个厂商服务器对接文档就不一样),以下实战中会有所省略这部分内容 - 数据存储到mongo数据库,可看我这篇文章:
云服务器下载安装mongo数据库并远程连接详细图文版本(全) Java关于MongoTemplate的增删改查实战代码解析(全) - 前端form表单的传输规范字段值(可以获取拿到什么字段以及传输显示什么字段等)
定义一个接口类:
public interface FileUploadService {
以及接口实现类:
@Slf4j
@Service
public class FileUploadServiceImpl implements FileUploadService {
3.1 上传文件
此处的上传可以通过任意位置,上传到字节流,在上传到服务器(上面的本地测试是写死某个路径)
ResultGeneralModel这个类,是自个封装的json格式返回结果(此处就不post出这个实体类) 类似的可以通过上面json或者定义一个map集合封装json格式仿照下
@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();
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<>();
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;
}
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) {
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);
}
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");
}
}
3.2 下载文件
思路:由于mongo的数据库以及存储了下载链接,通过查询mongo的数据库,之后对应的链接进行重定向即可下载
controller类:
@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();
}
3.3 删除文件
思路:上传与删除一样,需要对服务器进行操作之后在对数据库进行操作
服务器的操作需要对接aws s3(省略) 删除数据库的代码模块(通过form表单 获取对应的某个字段来或者这一行数据,之后删除这一行数据即可)
@Override
public void deletemongo(String id) {
Query query = new Query(Criteria.where("_id").is(id));
mongoTemplate.remove(query, SdkInfo.class);
}
3.4 显示所有文件
对应回显在前端是通过什么字段值,做好规范约束 前端的规范约束是这个:
*接口需要支持的参数:
page: Int // 当前页码
size: Int // 每页条数
*接口返回的字段规范如下:
{
code:0,
data:{
list:[
{
id: Int|String,
...
}
]
}
}
对应接口规范,代码为:
private final MongoService mongoService;
@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();
接口实现类:
@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;
}
|