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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用版 2核4GB 50GB
简介: 前言:在我另一篇笔记中已经记载了如何用nginx + vsftp搭建图片服务器(请参考nginx + vsftp搭建图片服务器),并且用vsftp的客户端工具filezilla测试过已经可用。

前言:

在我另一篇笔记中已经记载了如何用nginx + vsftp搭建图片服务器(请参考nginx + vsftp搭建图片服务器),并且用vsftp的客户端工具filezilla测试过已经可用。但是在开发中应该是把用户在前端页面提交的图片保存到图片服务器中,接下来就来实现这个功能。点
下载源码

需求:

用户在页面中上传一张图片,把图片保存到图片服务器,把图片的url保存到user表中,复制user表中的图片url在浏览器中可访问到用户上传的图片。

功能实现:

一、数据库设计:

img_a22c1ef0c1956ede8e19ffde3a4783d8.png
图片发自简书App

二、项目设计:

为了快速开发,本案例使用springboot + mybatis实现。项目结构如下:


img_f7f6607485f3e48d0d870a2b7a16de5d.png
图片发自简书App

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,看看上传之前的目录结构:

img_4422b91c9e20944c78902fb9fdf044b0.png
图片发自简书App

②:运行项目,上传文件:

img_3c7ecafdd3094e7bae034933e2b0a9b4.png
图片发自简书App

img_21d8662c98c562eaf35fb25614f07e03.png
图片发自简书App

③:返回了success,再到filezilla中刷新一下,看看图片是否成功上传到服务器:

img_c8edf8c0637bebe50d04e592de3dcd85.png
图片发自简书App

④:已经上传成功了。再看看数据表中的信息:

img_d3910079b33d635be9e333f71cec1b93.png
图片发自简书App

⑤:再复制数据表中保存的图片url到浏览器中。看看能否访问到图片:

img_113e8aa95b26742d6033ea99e800de09.png
图片发自简书App

成功访问到了刚才上传的图片!

总结:

1、过程梳理:
先搭建起项目,在html页面中通过<input type="file">上传文件,在controller中通过MultipartFile对象接收图片信息,然后获取原文件名,调用IDUtis工具类生成新的文件名,调用joda-time时间组件获取当前时间作为图片在服务器端保存的目录,然后用@Value读取在application.properties中的配置信息,拼接出图片的url,调用service保存到数据库中。最后调用ftp工具类,new了一个ftp的客户端,传入相关参数,把图片上传到图片服务器。

2、避坑说明:
在上面已经说过了,在ftp工具类中,一定要加上ftp.enterLocalPassiveMode()设置被动模式,不然上传到服务器的就是空文件,大小一直是0字节。

以上内容属于个人笔记整理,如有错误,欢迎批评指正!

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
9天前
|
搜索推荐 Java 索引
Java中的服务器端渲染(SSR)
Java中的服务器端渲染(SSR)
|
12天前
|
安全 前端开发 Java
Java中的服务器端渲染(SSR)技术深入剖析
Java中的服务器端渲染(SSR)技术深入剖析
|
13天前
|
Prometheus 监控 Cloud Native
Java 服务挂掉,服务器异常宕机问题排查
Java 服务挂掉,服务器异常宕机问题排查
18 1
|
3天前
|
Java 数据格式
Java面试题:简述Java Socket编程的基本流程,包括客户端和服务器的创建与通信。
Java面试题:简述Java Socket编程的基本流程,包括客户端和服务器的创建与通信。
11 0
|
8天前
|
前端开发 应用服务中间件 Linux
Nginx15-16服务器启停命令
Nginx15-16服务器启停命令
|
11天前
|
缓存 Java 数据库
Java中的服务器端渲染(SSR)优化与实现
Java中的服务器端渲染(SSR)优化与实现
|
12天前
|
搜索推荐 Java 索引
Java中的服务器端渲染(SSR)
Java中的服务器端渲染(SSR)
|
12天前
|
缓存 搜索推荐 Java
Java中的服务器端渲染(SSR)原理与实现
Java中的服务器端渲染(SSR)原理与实现
时间轮-Java实现篇
在前面的文章《[时间轮-理论篇](https://developer.aliyun.com/article/910513)》讲了时间轮的一些理论知识,然后根据理论知识。我们自己来实现一个简单的时间轮。
|
3天前
|
Java 调度
Java线程的六种状态
Java线程有六种状态: 初始(NEW)、运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、超时等待(TIMED_WAITING)、终止(TERMINATED)。
13 1