写博客免费的图床工具图片总失效?也许你可以自己创造一个图床工具

创建自己的图床工具

Posted by LY on July 30, 2019

欢迎大家关注我的以下主页,尤其是今日头条!!!谢谢🙏🙏🙏

csdn:雷园的csdn博客

个人博客:雷园的个人博客

简书:雷园的简书

今日头条:来自底层程序员的仰望

项目背景

  1. 在这里呢,我简单的说一下什么是图床。

  2. 经常写博客的同志们肯定会经常性的贴一些图片到博客当中,但是如果将图片跟随博文一起上传,那么会造成博客体积越来越大。

  3. 这个时候呢就该我们的图床上场了。

  4. 以前呢我经常用的是mac客户端的iPic,也确实是非常的好用尤其是可以与Typora结合,可以自动上传并转换图片地址为外网链接。

  5. 但是免费的图床时隔一段时间后,图片会自动失效,会导致你的博客成了这个样子:

    失效图

    属实是非常的难受,因为有好多东西我都不记得了。。。这个样子像是丢失了记忆一样。

  6. 当然啦,他也不是只有免费的,还有收费,那自然是非常的好用了,方便快捷速度也很快,不过要收费的。因为个人收入不高,所以没有花钱去支持他们,只好自己动手做一个简易的图床啦。

项目简介

  1. 虽然项目非常简单,但是我还是简单的介绍一下技术选型。
    1. 编程语言我选择了我最擅长的Java
    2. 服务端框架我选择了构建速度最快的SpringBoot
    3. 前端代码非常简单,我选择了HTMLJavaScript以及jquery
    4. 文件服务我选择了FastDfs以及Nginx
    5. 服务器版本我选择了Tomcat9
  2. 云主机”选型。
    1. 这里为什么打上双引号呢,因为云主机呢,着实是挺贵的,所以呢我选择了在本地PC上搭建,然后通过一台低配置的VPS进行端口映射,实现外网访问。
    2. 但是呢本地的PC、运行这些个东西也挺是费劲的,所以我还选择了使用Docker实现部署。
    3. 可见呢项目虽小却也是“五脏俱全”
  3. 简单说一下为什么要使用Docker
    1. 首先呢,FastDfs无论是配置还是启动运行,都比较费劲,我记得初次搭建的时候耗费了很长的时间。
    2. 经过一次搭建我就受够了它,所以我选择了使用DockerFile构建一个开箱即用的FastDfs镜像,这样以来无论到什么样的系统环境当中,只要可以运行Docker我就可以非常快速的构建起来一个fdfs文件服务系统。
    3. 多次的应用也证明了我的想法是没有错的。

项目功能描述

  1. 此项目主要是实现简单的图片拖拽上传,并返回上传后的图片链接以及预览图展示,可实现拖拽多图片实现同时上传,点击图片自动复制图片链接。效果图如下

    项目效果图

  2. 待实现功能:

    1. 分用户实现上传。
    2. 分用户实现上传记录。
    3. 分用户实现已上传文件管理。

项目结构

  1. web项目结构如下:

    web项目结构

    咳咳!!!不要笑不要笑~~

  2. Java服务端项目结构如下

    服务端项目结构

    同样也是非常的简单哈!

  3. 由上可见我们的项目结构是非常的简单的。

项目代码实现

  1. 虽然web项目只有一个页面,但是我们还是要说一下的哈。页面中的css我就不细说了,感兴趣的可以看一下我的js代码,不然的话直接Copy也可以的哦。

    <html lang="en">
    <style>
        body {
            height: 100%;
            padding: 0;
            margin: 0;
        }
        .outer {
            height: 100%;
            padding: 100px 0 0;
            box-sizing: border-box;
        }
       
        .A {
            height: 100px;
            margin: -100px 0 0;
            background: #BBE8F2;
        }
       
        .B {
            height: 100%;
            background: #D9C666;
        }
    </style>
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body class="outer">
    <div id="area" class="A">将图片拖拽到此区域</div>
    <div id="preview" class="B"></div>
    <script src="http://code.jquery.com/jquery-latest.js"></script>
    <script>
        $(document).on({
            dragleave: function (e) {      //拖离
                e.preventDefault();
            },
            drop: function (e) {           //拖后放
                e.preventDefault();
            },
            dragenter: function (e) {      //拖进
                e.preventDefault();
            },
            dragover: function (e) {       //拖来拖去
                e.preventDefault();
            }
        });
        var box = document.getElementById('area'); //拖拽区域
        box.addEventListener("drop",
            function (e) {
                e.preventDefault(); //取消默认浏览器拖拽效果
                var fileList = e.dataTransfer.files; //获取文件对象
                //检测是否是拖拽文件到页面的操作
                if (fileList.length == 0) {
                    return false;
                }
                //上传
                xhr = new XMLHttpRequest();
          			// 这里的false参数代表使用同步方式提交表单
                xhr.open("post", "http://ip:port/uploadFile", false);
                xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
          			// 创建一个表单
                var fd = new FormData();
                //检测文件是不是图片,同样服务端也可以对文件类型做限制,但实际上fdfs对于上传文件并无格式要求
                for (var i = 0; i < fileList.length; i++) {
                    if (fileList[i].type.indexOf('image') === -1) {
                        alert("您拖的不是图片!");
                        return false;
                    }
                    // 因为实现多图片上传,所以循环将所有图片都添加到files元素当中
                    fd.append('files', fileList[i]);
                }
          			// 提交表单
                xhr.send(fd);
          			// 等待请求结果
                if (xhr.status === 200) {
                  	// 若服务端返回code为SUC则代表处理成功
                    if ($.parseJSON(xhr.responseText).code == "SUC") {
                        var str = "";
                      	// 对返回的图片链接列表urlList进行处理
                        for (var i = 0; i < $.parseJSON(xhr.responseText).urlList.length; i++) {
                          // 动态拼接html元素,元素内容为上传后的预览图片以及图片链接 
                            str = str + "<a href='javascript:void(0);' onclick='CopyUrl(" + i + ")'><img style='width: 150px' src='" + $.parseJSON(xhr.responseText).urlList[i] + "'/></a><e>点击图片复制链接:<input  id='img" + i + "'  style='width: 50%' value=' " + $.parseJSON(xhr.responseText).urlList[i] + "' readonly/></e><br/>";
                        }
                        // 将拼接结果展示到页面中
                        $("#preview").html(str);
                    }
                }
            }, false
        );
      	// 实现点击图片自动复制图片链接
        function CopyUrl(id) {
            document.getElementById("img" + id).select();
            document.execCommand("copy");
        }
    </script>
    </body>
    </html>
    
  2. 接下来我们就说一下服务端的Java代码实现:

    1. 先来说一下Idea下的SpringBoot项目创建吧,说的仔细一点虽然啰嗦却也是对小白的一种福利,大佬们尽管跳过这段好了。

      项目创建-1

      项目闯进啊-2

      项目创建-3

    2. 依照此步骤创建项目即可,接下来我们说一下代码。

    3. 先来说一下我们需要手动引入的依赖,先来看pom.xml

      <!-- druid -->
      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
      </dependency>
      <!-- fdfs -->
      <dependency>
        <groupId>com.github.tobato</groupId>
        <artifactId>fastdfs-client</artifactId>
        <version>1.26.2</version>
      </dependency>
      <!-- lombok -->
      <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
      </dependency>
      
    4. 提前解惑:虽然我们当前的版本中暂时没有用涉及到数据库存储,但我们后期会加入相关的数据留存以及权限等等。而且我们在项目创建中选择了mysql数据库,根据springboot的启动要求我们必须在启动配置项当中配置数据库相关参数,否则无法正常启动项目。而为了日后方便拓展开发,我们选择使用阿里的Druid数据源进行配置。关于为什么引入lombok,完全是因为个人习惯,虽然我们的项目结构中只有一个vo类,但难免以后会增多,加入lombok省去了我们很多的麻烦,虽然可以IDEA可以自动生成,但是也比不过什么都不用操作来的好!!!而FDFS相关包是我们当前版本的核心,我们需要依赖于这个包进行文件的上传等操作。

    5. 接下来我们看一下项目配置,application.yml。当然大家也可以使用application.properties,个人比较习惯使用yml。

      server:
        port: 8081
      spring:
        datasource:
          type: com.alibaba.druid.pool.DruidDataSource
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://192.168.6.23:3306/qingongzhuxue?characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false
          username: root
          password: root
          thymeleaf:
            prefix: classpath:/templates/
            suffix: .html
            mode: HTML5
      fdfs:
        connect-timeout: 600
        so-timeout: 1500
        thumb-image:
          width: 150
          height: 150
        tracker-list:
          - fdfs:22122
        # 这个配置项是我自定义的配置项,后面的代码中会用到
        head:
          url: http://ip:6001/
      
    6. 来看一下我们唯一的vo类,FileUploadResponse.java

      package com.leiyuan.tuchuang.vo;
            
      import lombok.Data;
            
      import java.util.List;
            
      /**
       * ////////////////////////////////////////////////////////////////////
       * //                          _ooOoo_                               //
       * //                         o8888888o                              //
       * //                         88" . "88                              //
       * //                         (| ^_^ |)                              //
       * //                         O\  =  /O                              //
       * //                      ____/`---'\____                           //
       * //                    .'  \\|     |//  `.                         //
       * //                   /  \\|||  :  |||//  \                        //
       * //                  /  _||||| -:- |||||-  \                       //
       * //                  |   | \\\  -  /// |   |                       //
       * //                  | \_|  ''\---/''  |   |                       //
       * //                  \  .-\__  `-`  ___/-. /                       //
       * //                ___`. .'  /--.--\  `. . ___                     //
       * //              ."" '<  `.___\_<|>_/___.'  >'"".                  //
       * //            | | :  `- \`.;`\ _ /`;.`/ - ` : | |                 //
       * //            \  \ `-.   \_ __\ /__ _/   .-` /  /                 //
       * //      ========`-.____`-.___\_____/___.-`____.-'========         //
       * //                           `=---='                              //
       * //      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        //
       * //            佛祖保佑       永不宕机     永无BUG                    //
       * ////////////////////////////////////////////////////////////////////
       *
       * @author 来自底层程序员的仰望
       * @ClassName FileUploadResponse
       * @date 2019-07-29 15:49
       * <p></p>
       **/
      @Data
      public class FileUploadResponse {
          // 错误代码
          private String code;
          // 上传成功后的图片链接列表
          private List<String> urlList;
      }
      

      嗯!!!就是这个效果,拜倒在我的注释之下吧!!!

    7. 然后就是看看我们唯一的ControllerTestController.java

      package com.leiyuan.tuchuang.controller;
            
      import com.leiyuan.tuchuang.vo.FileUploadResponse;
      import com.github.tobato.fastdfs.domain.StorePath;
      import com.github.tobato.fastdfs.service.FastFileStorageClient;
      import org.apache.commons.io.FilenameUtils;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.web.bind.annotation.CrossOrigin;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RestController;
      import org.springframework.web.multipart.MultipartFile;
            
      import java.io.IOException;
      import java.util.ArrayList;
      import java.util.List;
            
      /**
       * ////////////////////////////////////////////////////////////////////
       * //                          _ooOoo_                               //
       * //                         o8888888o                              //
       * //                         88" . "88                              //
       * //                         (| ^_^ |)                              //
       * //                         O\  =  /O                              //
       * //                      ____/`---'\____                           //
       * //                    .'  \\|     |//  `.                         //
       * //                   /  \\|||  :  |||//  \                        //
       * //                  /  _||||| -:- |||||-  \                       //
       * //                  |   | \\\  -  /// |   |                       //
       * //                  | \_|  ''\---/''  |   |                       //
       * //                  \  .-\__  `-`  ___/-. /                       //
       * //                ___`. .'  /--.--\  `. . ___                     //
       * //              ."" '<  `.___\_<|>_/___.'  >'"".                  //
       * //            | | :  `- \`.;`\ _ /`;.`/ - ` : | |                 //
       * //            \  \ `-.   \_ __\ /__ _/   .-` /  /                 //
       * //      ========`-.____`-.___\_____/___.-`____.-'========         //
       * //                           `=---='                              //
       * //      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        //
       * //            佛祖保佑       永不宕机     永无BUG                    //
       * ////////////////////////////////////////////////////////////////////
       *
       * @author 雷园
       * @ClassName TestController
       * @date 2019-07-29 14:59
       * <p></p>
       **/
      // 设置全局返回为json
      @RestController("/")
      // 设置允许跨域访问
      @CrossOrigin(origins = "*", maxAge = 3600)
      public class TestController {
        	// 自动注入fdfs
          @Autowired
          private FastFileStorageClient storageClient;
      		// 获取配置文件中的值,也就是上面所说的我自定义的配置变量
          @Value("${fdfs.head.url}")
          private String url;
          /**
           * 
           * @param files 文件数组
           * @return FileUploadResponse
           * @throws IOException 抛出IO异常
           */
          @PostMapping(value = "/uploadFile")
          public FileUploadResponse uploadFile(MultipartFile[] files) throws IOException {
            	// 初始化一个列表
              List<String> urlList = new ArrayList<>();
            	// 循环文件数组将文件上传
              for (MultipartFile file : files) {
                	// 文件上传
                  StorePath storePath = storageClient.uploadFile(file.getInputStream(), file.getSize(),FilenameUtils.getExtension(file.getOriginalFilename()), null);
                	// 拼接图片地址
                  urlList.add(url + storePath.getGroup() + "/" + storePath.getPath());
              }
            	// 组合返回数据
              FileUploadResponse fileUploadResponse = new FileUploadResponse();
              fileUploadResponse.setCode("SUC");
              fileUploadResponse.setUrlList(urlList);
              return fileUploadResponse;
          }
      }
      
    8. 最后我们看一下启动类:TuchuangApplication.java

      package com.leiyuan.tuchuang;
            
      import com.github.tobato.fastdfs.FdfsClientConfig;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.context.annotation.EnableMBeanExport;
      import org.springframework.context.annotation.Import;
      import org.springframework.jmx.support.RegistrationPolicy;
      // 这里加入fdfs配置类
      @Import(FdfsClientConfig.class)
      // 解决启动异常的问题
      @EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
      @SpringBootApplication
      public class TuchuangApplication {
          public static void main(String[] args) {
              SpringApplication.run(TuchuangApplication.class, args);
          }
      }
      
  3. 到这里,我们的代码算是写完了,接下来就是使用Docker进行部署了。

结束语

  1. 此篇幅有些过长了,有关部署的部分,大家请查看我的下一篇文章,如何利用Docker快捷部署私人图床工具
  2. 作为程序员的我当然对于算法分析以及Java、Python、Go同样有着浓厚的兴趣,相信我们可以在技术的道路上走的更远。
  3. 对于Docker还要多说两句,作者最近在学习和应用docker-compose编排以及docker swarm集群部署,手头也有很多闲置的服务器用来练手,希望同样感兴趣的同学们可以私我或者评论我们多多交流学习心得。
  4. 谢谢大家!!!