Maven依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 文件上传 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.3</version>
</dependency>
<!-- 时间操作组件 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.5</version>
</dependency>

一、本地

Java实现把图片上传到本地

配置文件

path.properties配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 文件保存公共路径
data.store.local=/home/***/data

# 文章图片(题图)
articlePath=article
# 文章图片(配图)
illustrationPath=illustration
# 用户头像
userPath=user
# 用户相册
photoPath=photo

# 允许上传的文件后缀名,不包含"."
#data.store.local.suffix=png,jpg,jpeg
data.store.local.suffix=*

调用

ArticleController.java的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RequestMapping("/api/rest/nanshengbbs/v3.0/article")
@Controller
public class ArticleController {
@Autowired
FileUploadUtil fileUploadUtil;
@Autowired
PathUtil pathUtil;
@PostMapping("/setArticle")
@ResponseBody
public ReturnT<?> setArticle(@RequestParam(value = "picture", required = false)) {
try {
// 保存文件
String newFileName = fileUploadUtil.save(file, pathUtil.getArticlePath());

return ReturnT.success("发布文章成功");
} catch (Exception e) {
logger.error("发布文章失败");
return ReturnT.fail("发布文章失败");
}
}
}

// 结果:文件保存到C:\home\nanshengbbs\data\article 或者 /home/nanshengbbs/data/article

工具类

FileUploadUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@PropertySource({"classpath:path.properties"})
@Component
public class FileUploadUtil {
private static Logger logger = LoggerFactory.getLogger(FileUploadUtil.class);

// 文件存储根路径
@Value("${data.store.local}")
private String rootPath;
// 允许上传的文件后缀名
@Value("${data.store.local.suffix}")
private String suffix;

private List<String> suffixList = null;

@PostConstruct
private void init(){
if (StringUtil.isNotEmpty(suffix)){
suffixList = Stream.of(suffix.split("(, *)+"))
.map(s -> s.trim().toLowerCase()).collect(Collectors.toList());
}
}

/**
* 保存文件
* @param file 源文件
* @param folder 文件夹名称(介于前者与后者之间)
* @return
*/
public String save(MultipartFile file, String folder){
String orginalName = file.getOriginalFilename();
if (checkFile(orginalName)){ // 检查文件后缀名
// 构造存储文件名
String fileName = generateFileName(orginalName);
// 构造目标文件路径
String targetPath = generateStorePath(folder, fileName);
// 保存文件到指定目录
String path = storeData(targetPath, file);
logger.info("已保存文件:" + path);

return path;
} else {
logger.info("不支持上传该类型的文件:" + orginalName);
return null;
}
}

/**
* 删除文件
* @param fulPath 全路径
*/
public static void del(String fulPath){
// 封装上传文件位置的全路径
File targetFile = new File(fulPath);
// 删除文章对应的图片(实际删除)
targetFile.delete();
}

/**
* 构造存储文件名
* @param orginalName 原始文件名
* @return 返回新的文件名
*/
public String generateFileName(String orginalName){
return DateUtil.getNowDate() + "_" + orginalName;//“上传时间_文件名”
}

/**
* 构造目标文件路径
* @param folder 文件夹名称(介于前者与后者之间)
* @param fileName 文件名
* @return 目标文件路径
*/
public String generateStorePath(String folder, String fileName){
return Paths.get(rootPath, folder, fileName).toString();
}

/**
* 保存文件到指定目录
* @param path 目标目录
* @param file 文件
* @return
*/
public String storeData(String path, MultipartFile file){
File stored = new File(path);
try (InputStream in = file.getInputStream();){
FileUtils.copyInputStreamToFile(in, stored);
return stored.getCanonicalPath();
} catch (IOException e) {
logger.error(e.getMessage(), e);
return null;
}
}

/**
* 检查文件后缀名
* @param fileName 文件名
* @return 通过检查为true,不通过为false
*/
public boolean checkFile(String fileName){
if (suffixList.size() == 1 && suffixList.get(0).equals("*")){
return true;
}
String ext = FilenameUtils.getExtension(fileName);
return suffixList.contains(ext.toLowerCase());
}
}

StringUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class StringUtil {
/**
* 字符串为空
* @param data
* @return
*/
public static boolean isEmpty(String data){
return data == null || "".equals(data);
}

/**
* 字符串不为空
* @param data
* @return
*/
public static boolean isNotEmpty(String data){
return !isEmpty(data);
}
}

PathUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@PropertySource({"classpath:path.properties"})
@Component
public class PathUtil {
// 文章图片(题图)
@Value("${articlePath}")
private String articlePath;
// 文章图片(配图)
@Value("${illustrationPath}")
private String illustrationPath;
// 用户头像
@Value("${userPath}")
private String userPath;
// 用户相册
@Value("${photoPath}")
private String photoPath;

public String getArticlePath() {
return articlePath;
}

public void setArticlePath(String articlePath) {
this.articlePath = articlePath;
}

public String getIllustrationPath() {
return illustrationPath;
}

public void setIllustrationPath(String illustrationPath) {
this.illustrationPath = illustrationPath;
}

public String getUserPath() {
return userPath;
}

public void setUserPath(String userPath) {
this.userPath = userPath;
}

public String getPhotoPath() {
return photoPath;
}

public void setPhotoPath(String photoPath) {
this.photoPath = photoPath;
}
}

二、图片服务器

Java实现把图片上传到图片服务器(nginx+vsftp)

nginx搭建参考:搭建http服务器-nginx

vsftp搭建参考:Vsftp安装与配置

配置文件

path.properties配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ftp相关配置
ftp.address=*.*.*.*
ftp.port=21
ftp.username=ftpuser
ftp.password=******

# 文件保存公共路径
data.store.local=/home/ftpuser/***/data
# 图片服务器相关配置
image.base.url=http://*.nanshengbbs.top/***/data

# 文章图片(题图)
articlePath=article
# 文章图片(配图)
illustrationPath=illustration
# 用户头像
userPath=user
# 用户相册
photoPath=photo

调用

ArticleController.java的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@RequestMapping("/api/rest/nanshengbbs/v3.0/article")
@Controller
public class ArticleController {
@Autowired
FtpUtil ftpUtil;
@Autowired
PathUtil pathUtil;
@PostMapping("/setArticle")
@ResponseBody
public ReturnT<?> setArticle(@RequestParam(value = "picture", required = false)) {
try {
// 构造文件名
String fileName = UUIDUtil.getRandomUUID() + file.getOriginalFilename()
.substring(file.getOriginalFilename().lastIndexOf("."));
// 调用FtpUtil工具类进行上传
String newFileName = ftpUtil.uploadFile(pathUtil.getArticlePath(),
fileName, file.getInputStream());

return ReturnT.success("发布文章成功");
} catch (Exception e) {
logger.error("发布文章失败");
return ReturnT.fail("发布文章失败");
}
}
}

// 结果:文件保存到(/home/ftpuser/nanshengbbs/data/article)

工具类

FtpUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;

@PropertySource({"classpath:path.properties"})
@Component
public class FtpUtil {
// FTP服务器ip
@Value("${ftp.address}")
private String host;
// FTP服务器端口
@Value("${ftp.port}")
private int port;
// FTP登录账号
@Value("${ftp.username}")
private String username;
// FTP登录密码
@Value("${ftp.password}")
private String password;
// FTP服务器基础目录(/home/ftpuser/nanshengbbs/data)
@Value("${data.store.local}")
private String basePath;
// 图片服务器相关配置
@Value("${image.base.url}")
private String imageUrl;

/**
* 向FTP服务器上传文件
* @param filePath FTP服务器文件存放路径。例如文章配图:/article。
* 文件的路径为basePath+filePath
* @param fileName 上传到FTP服务器上的文件名
* @param input 输入流
* @return 成功返回图片访问的全路径,否则返回null
*/
public String uploadFile(String filePath, String fileName, InputStream input) {
String result = null;
FTPClient ftp = new FTPClient();
try {
// 连接FTP服务器(如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器)
ftp.connect(host, port);
// 登录
ftp.login(username, password);
// 获取应答code
int reply = ftp.getReplyCode();
// 判断是否成功连接(成功:200 <= n <= 300)
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return result;
}

// 切换到上传目录
if (!ftp.changeWorkingDirectory(Paths.get(basePath, filePath).toString())) {
// 如果目录不存在创建目录
String[] dirs = filePath.split("/");
String tempPath = basePath;
for (String dir : dirs) {
if (null == dir || "".equals(dir)) continue;
tempPath += "/" + dir;
if (!ftp.changeWorkingDirectory(tempPath)) {
if (!ftp.makeDirectory(tempPath)) {
return result;
} else {
ftp.changeWorkingDirectory(tempPath);
}
}
}
}

/**
* 一定要加上ftp.enterLocalPassiveMode()设置被动模式,否则的话会出现图片传到服务器上去
* 了,但是大小一直是0。这个方法的意思就是每次数据连接之前,ftp client告诉ftp server开通
* 一个端口来传输数据。为什么要这样做呢,因为ftp server可能每次开启不同的端口来传输数据,但
* 是在linux上或者其他服务器上面,由于安全限制,可能某些端口没有开启,所以就出现阻塞。
*/
// 设置为被动模式
ftp.enterLocalPassiveMode();
// 设置上传文件的类型为二进制类型
ftp.setFileType(FTP.BINARY_FILE_TYPE);
// 去中文
fileName = CommonUtil.getRemoveChinese(fileName);
// 上传文件
if (!ftp.storeFile(fileName, input)) {
return result;
}
result = imageUrl + "/" + filePath + "/" + fileName;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
input.close();
ftp.logout();
if (ftp.isConnected()) {
ftp.disconnect();
}
} catch (Exception e) {}
}
System.out.println(result);
return result;
}
}

三、第三方

Java实现把图片上传到七牛云对象存储

七牛官网:https://developer.qiniu.com/

对象存储Java SDK:https://developer.qiniu.com/kodo/sdk/1239/java

配置文件

76.properties配置如下:

1
2
3
# 配置你自己七牛云对应的AccessKey和SecretKey
AccessKey=**************************************
SecretKey==**************************************

path.properties配置如下:

1
2
# 七牛对象存储对应的域名
76.image.domain=http://*.nanshengbbs.top

调用

ArticleController.java的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RequestMapping("/api/rest/nanshengbbs/v3.0/article")
@Controller
public class ArticleController {
@Autowired
FtpUtil ftpUtil;
@Autowired
PathUtil pathUtil;
@PostMapping("/setArticle")
@ResponseBody
public ReturnT<?> setArticle(@RequestParam(value = "picture", required = false)) {
try {
// 构造文件名
String fileName = UUIDUtil.getRandomUUID() + file.getOriginalFilename();
// 上传到七牛
String newFileName = QiniuUtil.upload(file.getInputStream(), fileName);

return ReturnT.success("发布文章成功");
} catch (Exception e) {
logger.error("发布文章失败");
return ReturnT.fail("发布文章失败");
}
}
}

// 结果:文件保存到(七牛云->对象存储->文件管理)

工具类

QiniuUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import com.google.gson.Gson;
import com.qiniu.http.Response;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;

import java.io.IOException;
import java.io.InputStream;

public class QiniuUtil {
// 需要操作的账号的AK和SK
private static final String ACCESS_KEY =
PropertyUtil.getProperties76().getProperty("AccessKey");
private static final String SECRET_KEY =
PropertyUtil.getProperties76().getProperty("SecretKey");
// 要上传的空间
private static final String bucket = "nanshengbbs";
// 密钥
private static final Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
private static final String upToken = auth.uploadToken(bucket);
// 构造一个带指定 Region 对象的配置类(华南)
private static final Configuration cfg = new Configuration(Region.huanan());
// 创建上传对象
private static final UploadManager uploadManager = new UploadManager(cfg);

/**
* 普通上传(上传本地文件)
* @param localFilePath 文件全路径
* @param fileName 七牛保存的文件名
* @throws IOException
*/
public static String upload(String localFilePath, String fileName) throws Exception {
// 调用put方法上传
Response res = uploadManager.put(localFilePath, fileName, upToken);
// 解析上传成功的结果
DefaultPutRet putRet = new Gson()
.fromJson(res.bodyString(), DefaultPutRet.class);
return putRet.key;
}

/**
* 字节数组上传
* @param uploadBytes 文件字节数组
* @param fileName 七牛保存的文件名
* @throws IOException
*/
public static String upload(byte[] uploadBytes, String fileName) throws Exception {
// 调用put方法上传
Response res = uploadManager.put(uploadBytes, fileName, upToken);
// 解析上传成功的结果
DefaultPutRet putRet = new Gson()
.fromJson(res.bodyString(), DefaultPutRet.class);
return putRet.key;
}

/**
* 数据流上传
* @param inputStream 文件数据流
* @param fileName 七牛保存的文件名
* @throws IOException
*/
public static String upload(InputStream inputStream, String fileName)
throws Exception {
// 调用put方法上传
Response res = uploadManager.put(inputStream, fileName, upToken, null, null);
// 解析上传成功的结果
DefaultPutRet putRet = new Gson()
.fromJson(res.bodyString(), DefaultPutRet.class);
return putRet.key;
}
}