前言:
在我另一篇筆記中已經記載了如何用nginx + vsftp搭建圖檔伺服器(請參考
nginx + vsftp搭建圖檔伺服器),并且用vsftp的用戶端工具filezilla測試過已經可用。但是在開發中應該是把使用者在前端頁面送出的圖檔儲存到圖檔伺服器中,接下來就來實作這個功能。點
我下載下傳源碼。
需求:
使用者在頁面中上傳一張圖檔,把圖檔儲存到圖檔伺服器,把圖檔的url儲存到user表中,複制user表中的圖檔url在浏覽器中可通路到使用者上傳的圖檔。
功能實作:
一、資料庫設計:

圖檔發自簡書App
二、項目設計:
為了快速開發,本案例使用springboot + mybatis實作。項目結構如下:
1、添加依賴:
pom.xml:
<!-- 檔案上傳 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.2</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>
<!-- mybaties -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
2、配置:
application.properties:
注意,下面的basepath配置的是檔案上傳的根路徑,
/home/ftpuser/images
,圖檔都傳到這個目錄或其子目錄下,baseUrl是通路圖檔時的基礎Url,因為在搭建圖檔伺服器時我們設定了通路根目錄是
/home/ftpuser
,是以通路的基礎url就是
192.168.xx.xxx/images
#配置資料庫連接配接資訊
spring.datasource.url=jdbc:mysql:///db_demo?useUnicode=true&characterEncoding=utf8
spring.datasource.username=#
spring.datasource.password=#
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#Mybatis掃描接口對應的xml檔案
mybatis.mapper-locations=classpath:mappers/*.xml
#起别名。可省略寫mybatis的xml中的resultType的全路徑
mybatis.type-aliases-package=com.zhu.pojo
#配置檔案上傳器
spring.http.multipart.maxFileSize=100Mb
spring.http.multipart.maxRequestSize=100Mb
#ftp相關配置
FTP.ADDRESS=192.168.xx.xxx
FTP.PORT=21
FTP.USERNAME=ftpuser
FTP.PASSWORD=ftpuser
FTP.BASEPATH=/home/ftpuser/images
#圖檔伺服器相關配置
IMAGE.BASE.URL=http://192.168.xx.xxx/images
#視圖解析器
spring.mvc.view.prefix=/pages/
spring.mvc.view.suffix=.html
3、實體類:
省略set和get方法。
public class User {
private Integer uid;
private String username;
private String password;
private String picture;
}
4、dao層:
UserMapper.java
public interface UserMapper {
int insert(User record);
}
UserMapper.xml
<insert id="insert" parameterType="com.zhu.pojo.User">
insert into user (uid, username, password,
picture)
values (#{uid,jdbcType=INTEGER}, #{username,jdbcType=CHAR}, #{password,jdbcType=CHAR},
#{picture,jdbcType=CHAR})
</insert>
5、service層:
service層隻是簡單的調用dao層,儲存user對象。
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public void insertUser(User user) {
userMapper.insert(user);
}
}
6、utils:
①、FtpUtils.java:
我們知道filezilla是vsftp用戶端工具,輸入ip、端口、vsftp使用者的使用者名和密碼就可以連接配接上服務。那麼在Java中,我們就new一個用戶端,除了需要傳入以上四個值外,還需要基礎目錄、檔案存放路徑和檔案io流。還有一點特别注意,一定要加上
ftp.enterLocalPassiveMode()
設定被動模式,否則的話會出現圖檔傳到伺服器上去了,但是大小一直是0。這個方法的意思就是每次資料連接配接之前,ftp client告訴ftp server開通一個端口來傳輸資料。為什麼要這樣做呢,因為ftp server可能每次開啟不同的端口來傳輸資料,但是在linux上或者其他伺服器上面,由于安全限制,可能某些端口沒有開啟,是以就出現阻塞。
public class FtpUtil {
/**
* Description: 向FTP伺服器上傳檔案
* @param host FTP伺服器ip
* @param port FTP伺服器端口
* @param username FTP登入賬号
* @param password FTP登入密碼
* @param basePath FTP伺服器基礎目錄,/home/ftpuser/images
* @param filePath FTP伺服器檔案存放路徑。例如分日期存放:/2018/05/28。檔案的路徑為basePath+filePath
* @param filename 上傳到FTP伺服器上的檔案名
* @param input 輸入流
* @return 成功傳回true,否則傳回false
*/
public static boolean uploadFile(String host, int port, String username, String password, String basePath,
String filePath, String filename, InputStream input) {
boolean result = false;
FTPClient ftp = new FTPClient();
try {
int reply;
ftp.connect(host, port);// 連接配接FTP伺服器
// 如果采用預設端口,可以使用ftp.connect(host)的方式直接連接配接FTP伺服器
ftp.login(username, password);// 登入
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return result;
}
//切換到上傳目錄
if (!ftp.changeWorkingDirectory(basePath+filePath)) {
//如果目錄不存在建立目錄
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();
//設定上傳檔案的類型為二進制類型
ftp.setFileType(FTP.BINARY_FILE_TYPE);
//上傳檔案
if (!ftp.storeFile(filename, input)) {
return result;
}
input.close();
ftp.logout();
result = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return result;
}
}
②、IDUtils.java:
這個工具類是用來生成随機的檔案名。因為使用者上傳的時候,可能檔案名是一緻的,比如A使用者上傳了名為a.jpg的圖檔,B使用者也上傳了名為a.jpg的圖檔的話就會上傳失敗,會提示檔案已存在。是以需要生成一個随機的檔案名保證不重名。
public class IDUtils {
/**
* 生成随機圖檔名
*/
public static String genImageName() {
//取目前時間的長整形值包含毫秒
long millis = System.currentTimeMillis();
//long millis = System.nanoTime();
//加上三位随機數
Random random = new Random();
int end3 = random.nextInt(999);
//如果不足三位前面補0
String str = millis + String.format("%03d", end3);
return str;
}
}
7、controller:
在springmvc中,前端頁面送出的圖檔資訊會自動封裝在MultipartFile對象中,在這個controller中的通過MultipartFile對象擷取圖檔本來的檔案名,然後截取字尾,用工具類生成新的檔案名,再把字尾拼接上,然後通過@Value注解擷取
application.properties中配置的ftp相關的配置的值,調用ftp工具類進行圖檔的上傳,調用service把使用者資訊儲存到資料庫。
@Controller
public class UserContrller {
@Value("${FTP.ADDRESS}")
private String host;
// 端口
@Value("${FTP.PORT}")
private int port;
// ftp使用者名
@Value("${FTP.USERNAME}")
private String userName;
// ftp使用者密碼
@Value("${FTP.PASSWORD}")
private String passWord;
// 檔案在伺服器端儲存的主目錄
@Value("${FTP.BASEPATH}")
private String basePath;
// 通路圖檔時的基礎url
@Value("${IMAGE.BASE.URL}")
private String baseUrl;
@Autowired
private UserService userService;
@RequestMapping("/pic/upload")
@ResponseBody
public String pictureUpload(
@RequestParam("pic") MultipartFile uploadFile,
@RequestParam("username")String username,
@RequestParam("password") String password) {
try {
//1、給上傳的圖檔生成新的檔案名
//1.1擷取原始檔案名
String oldName = uploadFile.getOriginalFilename();
//1.2使用IDUtils工具類生成新的檔案名,新檔案名 = newName + 檔案字尾
String newName = IDUtils.genImageName();
newName = newName + oldName.substring(oldName.lastIndexOf("."));
//1.3生成檔案在伺服器端存儲的子目錄
String filePath = new DateTime().toString("/yyyy/MM/dd");
//2、把前端輸入資訊,包括圖檔的url儲存到資料庫
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setPicture(baseUrl + filePath + "/" + newName);
userService.insertUser(user);
//3、把圖檔上傳到圖檔伺服器
//3.1擷取上傳的io流
InputStream input = uploadFile.getInputStream();
//3.2調用FtpUtil工具類進行上傳
boolean result = FtpUtil.uploadFile(host, port, userName, passWord, basePath, filePath, newName, input);
if(result) {
return "success";
}else {
return "false";
}
} catch (IOException e) {
return "false";
}
}
}
8、index.html:
這個頁面隻有一點需要注意,那就是form中需要加上
enctype="multipart/form-data"
,不然controller中通過MultipartFile擷取不到圖檔資訊。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="/pic/upload" enctype="multipart/form-data" method="post">
<input type="text" name="username"><br>
<input type="password" name="password"><br>
<input type="file" name="pic"><br>
<input type="submit" value="送出">
</form>
</body>
</html>
9、測試:
①:先用filezilla連上vsftp,看看上傳之前的目錄結構:
②:運作項目,上傳檔案:
③:傳回了success,再到filezilla中重新整理一下,看看圖檔是否成功上傳到伺服器:
④:已經上傳成功了。再看看資料表中的資訊:
⑤:再複制資料表中儲存的圖檔url到浏覽器中。看看能否通路到圖檔:
成功通路到了剛才上傳的圖檔!
總結:
1、過程梳理:
先搭建起項目,在html頁面中通過
<input type="file">
上傳檔案,在controller中通過MultipartFile對象接收圖檔資訊,然後擷取原檔案名,調用IDUtis工具類生成新的檔案名,調用joda-time時間元件擷取目前時間作為圖檔在伺服器端儲存的目錄,然後用@Value讀取在
中的配置資訊,拼接出圖檔的url,調用service儲存到資料庫中。最後調用ftp工具類,new了一個ftp的用戶端,傳入相關參數,把圖檔上傳到圖檔伺服器。
2、避坑說明:
在上面已經說過了,在ftp工具類中,一定要加上
ftp.enterLocalPassiveMode()
設定被動模式,不然上傳到伺服器的就是空檔案,大小一直是0位元組。