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

前言:

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

需求:

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


欢迎大家关注我的公众号 javawebkf,目前正在慢慢地将简书文章搬到公众号,以后简书和公众号文章将同步更新,且简书上的付费文章在公众号上将免费。


功能实现:

一、数据库设计:

图片发自简书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字节。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容