Java实现把图片上传到图片服务器(nginx+vsftp)前言:需求:功能实现:总结:

前言:

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

需求:

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

功能实现:

一、数据库设计:

图片发自简书App

二、项目设计:

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

图片发自简书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,看看上传之前的目录结构:

图片发自简书App

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

图片发自简书App

图片发自简书App

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

图片发自简书App

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

图片发自简书App

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

图片发自简书App

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

总结:

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

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术博客

Log4Net异常日志记录在asp.net mvc3.0的应用

log4net是.Net下一个非常优秀的开源日志记录组件。log4net记录日志的功能非常强大。它可以将日志分不同的等级,以不同的格式,输出到不同的媒介。本文主...

801
来自专栏零基础使用Django2.0.1打造在线教育网站

零基础使用Django2.0.1打造在线教育网站(十三):邮箱验证码实现

努力与运动兼备~~~有任何问题可以加我好友或者关注微信公众号,欢迎交流,我们一起进步!

1061
来自专栏xingoo, 一个梦想做发明家的程序员

[logstash-input-http] 插件使用详解

插件介绍 Http插件是2.0版本才出现的新插件,1.x是没有这个插件的。这个插件可以帮助logstash接收其他主机或者本机发送的http报文。 插件的原理很...

2548
来自专栏coding for love

在线商城项目16-头部前端逻辑修改

这是不合理的,我们应该根据登录态来做一个区别显示。未登录情况下显示login和购物车图标。已登录情况下显示用户名,logout,购物车图标。

421
来自专栏java学习

使用intellij idea搭建MAVEN+SSM(Spring+SpringMVC+MyBatis)框架

Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-On...

915
来自专栏有趣的django

Django REST framework+Vue 打造生鲜超市(八) 九、个人中心功能开发

九、个人中心功能开发 9.1.drf的api文档自动生成和 (1) url #drf文档,title自定义 path('docs',include_do...

3558
来自专栏PhpZendo

Symfony 服务容器:使用 XML 或 YAML 文件描述服务

本文是依赖注入(Depeendency Injection)系列教程的第 5 篇文章,本系列教程主要讲解如何使用 PHP 实现一个轻量级服务容器,教程包括:

541
来自专栏视觉求索无尽也

【Python】使用logging打日志

https://zhuanlan.zhihu.com/p/31893724 https://zhuanlan.zhihu.com/p/32043593

702
来自专栏PhpZendo

Symfony 服务容器:使用建造者创建服务

本文是依赖注入(Depeendency Injection)系列教程的第 5 篇文章,本系列教程主要讲解如何使用 PHP 实现一个轻量级服务容器,教程包括:

490
来自专栏自动化测试实战

接口测试框架——第一篇

3206

扫码关注云+社区