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

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 在我另一篇笔记中已经记载了如何用nginx + vsftp搭建图片服务器(请参考nginx + vsftp搭建图片服务器),并且用vsftp的客户端工具filezilla测试过已经可用。但是在开发中应该是把用户在前端页面提交的图片保存到图片服务器中,接下来就来实现这个功能。

功能实现:


一、数据库设计:


image.png


二、项目设计:


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


image.png


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


image.png


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


image.png

image.png

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


image.png


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


image.png

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


image.png


总结:


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
相关文章
|
1月前
|
Python
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
使用Python的socket库实现客户端到服务器端的图片传输,包括客户端和服务器端的代码实现,以及传输结果的展示。
137 3
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
|
1月前
|
负载均衡 算法 Java
java中nginx负载均衡配置
java中nginx负载均衡配置
38 0
文件上传 图片上传 客户端图片上传到服务器
文件上传 图片上传 客户端图片上传到服务器
|
3月前
|
Ubuntu 应用服务中间件 Linux
在Linux中,如何配置Web服务器(如Apache或Nginx)?
在Linux中,如何配置Web服务器(如Apache或Nginx)?
|
3月前
|
负载均衡 应用服务中间件 Linux
"揭晓nginx的神秘力量:如何实现反向代理与负载均衡,拯救服务器于水火?"
【8月更文挑战第20天】在Linux环境下,nginx作为高性能HTTP服务器与反向代理工具,在网站优化及服务器负载均衡中扮演重要角色。本文通过电商平台案例,解析nginx如何解决服务器压力大、访问慢的问题。首先介绍反向代理原理,即客户端请求经由代理服务器转发至内部服务器,隐藏真实服务器地址;并给出配置示例。接着讲解负载均衡原理,通过将请求分发到多个服务器来分散负载,同样附有配置实例。实践表明,采用nginx后,不仅服务器压力得到缓解,还提升了访问速度与系统稳定性。
82 3
|
3月前
|
存储 JavaScript 前端开发
Vue中实现图片上传,上传后的图片回显,存储图片到服务器 【使用对象存储OSS】
这篇文章介绍了在Vue中实现图片上传到阿里云OSS对象存储服务的完整流程,包括服务端签名直传的前提知识、后端设置、前端组件封装以及图片上传和回显的效果展示。
|
3月前
|
负载均衡 应用服务中间件 Apache
Nginx与Apache的终极对决:揭秘Web服务器界的“速度与激情”!你不可不知的性能霸主如何颠覆传统,震撼互联网的根基!
【8月更文挑战第13天】互联网技术发展中,Web服务器至关重要,Nginx与Apache是最广泛使用的两种。Apache历史悠久,但Nginx以轻量级和高性能脱颖而出。Nginx采用事件驱动架构,高效处理大量并发连接,而Apache使用进程驱动,高并发下资源消耗大。以餐厅为例,Nginx像大堂经理同时处理多个顾客需求,远比Apache为每位顾客分配服务员更高效。性能测试显示Nginx处理能力远超Apache。此外,Nginx配置简洁灵活,尤其在负载均衡方面表现突出。尽管Apache适合动态内容处理,但在快速变化的互联网环境中,Nginx因其独特优势成为许多开发者和企业的首选。
65 7
|
3月前
|
应用服务中间件 网络安全 nginx
运维专题.Docker+Nginx服务器的SSL证书安装
运维专题.Docker+Nginx服务器的SSL证书安装
128 3
|
3月前
|
数据可视化 Python
通过python建立一个web服务查看服务器上的文本、图片、视频等文件
通过python建立一个web服务查看服务器上的文本、图片、视频等文件
68 0
|
3月前
|
应用服务中间件 Linux 网络安全
在Linux中,如何配置Apache或Nginx Web服务器?
在Linux中,如何配置Apache或Nginx Web服务器?