Spring Boot文件上传之跨服务器上传文件

在实际项目中上传文件保存到到文件服务器是一个常用的操作,而在服务器上保存文件就需要特别小心。因为通常情况下不只是在一个路径里保存文件,所以需要实践一下保存文件到任意位置。当然,前提是你的应用程序有这样的操作权限。

一、分布式服务器上传作用

  • 数据库服务器:运行我们的数据库
  • 缓存和消息服务器:负责处理大并发访问的缓存和消息
  • 文件服务器:负责存储用户上传文件的服务器。
  • 应用服务器:负责部署我们的应用
  • 在实际开发中,我们会有很多处理不同功能的服务器。(注意:此处说的不是服务器集群)
  • 总结:分服务器处理的目的是让服务器各司其职,从而提高我们项目的运行效率。

分布式服务器工作示意图:

二、单独准备一个Tomcat作为文件服务器

除了项目之外,单独在准备一个Tomcat,如果和项目在同一个电脑上则需要对第二个Tomcat的端口作出修改,这里我使用虚拟机中Tomcat作为文件服务器使用,

由于我这里用的是虚拟机中的Tomcat,所以只需要设置非只读即可,如下:

  • 在web.xml中添加如下内容 :远程服务器中设置非只读
<init-param>
    <param-name>readonly</param-name>
    <param-value>false</param-value>
</init-param>

在Tomcat中webapps下创建upload目录,用于存放上传的图片

启动测试Tomcat是否可以正常访问:

三、创建springboot项目

这里我使用spring官方默认地址网络原因连接不上,就使用了阿里云的地址

输入项目名称和包信息,选择jdk版本,选择Maven项目

勾选spring web导入相关的依赖

四、导入依赖

在pom.xml中导入如下依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!--lombok依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!--添加druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <!--mybatis依赖-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <!--MySQL数据库连接的依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>

        <!--跨服务器上传文件依赖,切记导入,不然无法跨服务器上传-->
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-client</artifactId>
            <version>1.19.4</version>
        </dependency>

五、设置配置文件

在resources目录创建application.yml配置文件,内容如下

spring:
  datasource:
    # 使用阿里的Druid连接池
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 填写你数据库的url、登录名、密码和数据库名
    url: jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
  druid:
    # 连接池的配置信息
    # 初始化大小,最小,最大
    initial-size: 5
    min-idle: 5
    maxActive: 20
    # 配置获取连接等待超时的时间
    maxWait: 60000
    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    timeBetweenEvictionRunsMillis: 60000
    # 配置一个连接在池中最小生存的时间,单位是毫秒
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    # 打开PSCache,并且指定每个连接上PSCache的大小
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,wall,slf4j
    # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
    connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
    # 配置DruidStatFilter
    web-stat-filter:
      enabled: true
      url-pattern: "/*"
      exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
    # 配置DruidStatViewServlet
    stat-view-servlet:
      url-pattern: "/druid/*"
      # IP白名单(没有配置或者为空,则允许所有访问)
      allow: 127.0.0.1,192.168.8.109
      # IP黑名单 (存在共同时,deny优先于allow)
      deny: 192.168.1.188
      #  禁用HTML页面上的“Reset All”功能
      reset-enable: false
      # 登录名
      login-username: admin
      # 登录密码
      login-password: 123456
  servlet:
    multipart:
      #设置文件上传单个文件的大小
      max-file-size: 10MB
      #设置多个文件上传总文件的大小
      file-size-threshold: 100MB

#thymeleaf会自动设置前缀和后缀,这里就不需要手动添加了
#  thymeleaf:
#    suffix:
#    prefix:

mybatis:
  mapper-locations: classpath:mybatis/*.xml
  type-aliases-package: com.augus.pojo
server:
  servlet:
    context-path: /springboot09
  port: 8080

六、创建数据库

创建数据库存储新增的信息

CREATE TABLE `player` (
  `id` int(25) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `nickname` varchar(255) DEFAULT NULL,
  `photo` varchar(255) DEFAULT NULL,
  `filetype` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
)

七、在pojo包下创建player表的实体类

package com.augus.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class Player implements Serializable {
    private Integer id;
    private String username;
    private String password;
    private String nickname;
    private String photo;
    private String filetype;
}

八、在controller下创建FileUploadController,内容如下

package com.augus.controller;

import com.augus.pojo.Player;
import com.augus.service.PlayerService;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Controller
public class FileUploadController {
    //文件存储位置,定为私有不可修改的静态属性
    private final static String FILESERVER = "http://192.168.93.128:8080/upload/";

    @Autowired
    private PlayerService playerService;

    //打开信息提交页面
    @RequestMapping("/regist")
    public String testRegister(){
        return "userinformation";
    }

    @ResponseBody
    @RequestMapping("/fileUpload.do")
    public Map<String,String> fileUpload(MultipartFile headPhoto, HttpServletRequest request) throws IOException {
        //创建Map集合
        Map<String, String> map = new HashMap<String, String>();

        //控制文件大小,如果文件大小超过5m,就给前端返回提示
        //还有一种直接可以在springmvcxml中即可控制,但是不推荐,没有办法控制给前端的提示信息
        if(headPhoto.getSize()>1024*1024*5){
            map.put("message","文件大小不能超过5M");
            return map;
        }
        // 获取文件名,这里获取的是文件名,但是如果有文件名重复,则会出现覆盖,这时候在保存原文件的时候,就不同用原名字
        String originalFilename = headPhoto.getOriginalFilename();

        //那么就用UUID替换文件名
        String s = UUID.randomUUID().toString();

        //从获取上传进来的文件后缀名 例如 4.jpg 就取.jpg
        //lastIndexOf(".")截取文件中最后一个点,防止有多个.
        String extendsName = originalFilename.substring(originalFilename.lastIndexOf("."));

        //控制文件类型要是.jpg格式,如果不是则需要给前端返回提示信息
        if(!extendsName.equals(".jpg")){
            //指定返回值
            map.put("message", "图片类型必须是.jpg");
            return map;
        }

        // 使用 UUID+后缀名,拼接成文件名
        String newFileName = s + extendsName;

        //创建sun公司提供的jersey包中的client对象
        Client client = Client.create();
        //将存储地址和文件名组合
        WebResource resource = client.resource(FILESERVER+newFileName);
        //文件保存到另一个服务器, 将删除的图片以新名字提交的另一个图片服务器
        resource.put(String.class, headPhoto.getBytes());

        System.out.println("这是fileUpload");
        //设置上传成功时的响应
        map.put("message","上传成功");
        map.put("fileName",newFileName); //设置图片回显将图片的名字和格式传递过去
        map.put("fileType",headPhoto.getContentType()); //返回文件类型
        return map;
    }

    //添加信息
    @RequestMapping("/addUser")
    public String testAddUser(Player player){
        playerService.addUser(player);
        return "user";
    }
}

九、在service层处理如下

在service下创建PlayerService

package com.augus.service;

import com.augus.pojo.Player;

public interface PlayerService {
   int addUser(Player player);
}

在service下impl包下创建PlayerServiceImpl

package com.augus.service.impl;

import com.augus.mapper.PlayerMapper;
import com.augus.pojo.Player;
import com.augus.service.PlayerService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class PlayerServiceImpl implements PlayerService {
    //这个本来@Autowired保存,所以改用@Resource
    @Resource
    private PlayerMapper playerMapper;

    @Override
    public int addUser(Player player) {
        return playerMapper.addUser(player);
    }
}

十、在com.augus下次创建mapper包做处理如下

创建接口PlayerMapper

package com.augus.mapper;

import com.augus.pojo.Player;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface PlayerMapper {
    int addUser(Player player);
}

十一、在resources目录下创建mybatis包,存放mapper映射文件

创建PlayerMapper.xml,操作数据库,内容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.augus.mapper.PlayerMapper">
    <insert id="addUser">
        insert into player values(DEFAULT ,#{username},#{password},#{nickname},#{photo},#{filetype})
    </insert>
</mapper>

十二、在resources下的templates中存放html文件

userinformation.html:实现数据提交和图片上传,内容如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>userinformation</title>
    <style>
        .progress {
            width: 200px;
            height: 10px;
            border: 1px solid #ccc;
            border-radius: 10px;
            margin: 10px 0px;
            overflow: hidden;
        }
        /* 初始状态设置进度条宽度为0px */
        .progress > div {
            width: 0px;
            height: 100%;
            background-color: yellowgreen;
            transition: all .3s ease;
        }
    </style>
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(function(){
            $("#uploadFile").click(function(){
                // 获取要上传的文件
                var photoFile =$("#photo")[0].files[0]
                if(photoFile==undefined){
                    alert("您还未选中文件")
                    return;
                }
                // 将文件装入FormData对象
                var formData =new FormData();
                //注意这里写的headPhoto,在controller中也要用这个名字
                formData.append("headPhoto",photoFile)
                // ajax向后台发送文件
                $.ajax({
                    type:"post",
                    data:formData,
                    url:"fileUpload.do",
                    processData:false,
                    contentType:false,
                    // result能获取到后台返回的信息
                    success:function(result){
                        // 接收后台响应的信息,返回message键所对应的值
                        alert(result.message)
                        //设置图片回显  result.fileName获取的上传后的图片名称,这里要回显还需图片的地址
                        $("#headImg").attr("src","http://192.168.93.128:8080/upload/"+result.fileName)
                        //将文件类型和文件名放入form表单
                        //就是设置input标签value属性的值
                        $("#photoId").val(result.fileName)
                        $("#filetypeId").val(result.fileType)
                    },
                    //控制进度条进度
                    xhr: function() {
                        var xhr = new XMLHttpRequest();
                        //使用XMLHttpRequest.upload监听上传过程,注册progress事件,打印回调函数中的event事件
                        xhr.upload.addEventListener('progress', function (e) {
                            console.log(e);
                            //loaded代表上传了多少
                            //total代表总数为多少
                            var progressRate = (e.loaded / e.total) * 100 + '%';

                            //通过设置进度条的宽度达到效果
                            $('.progress > div').css('width', progressRate);
                        })

                        return xhr;
                    }
                })
            })
        })
    </script>
</head>
<body>
<form action="addUser" method="post">
    <table style="margin: auto;">
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="username" required><br></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="text" name="password" required><br></td>
        </tr>
        <tr>
            <td>昵称:</td>
            <td><input type="text" name="nickname" required><br></td>
        </tr>
        <tr>
            <td>头像:</td>
            <td><input id="photo" type="file"><br></td>
            <td>
                <div class="progress">
                    <div></div>
                </div>
            </td>
            <td>
                <a id="uploadFile" href="javascript:void(0)">立即上传</a><br>
            </td>
            <!--使用隐藏的输入框存储文件名称和文件类型-->
            <td><input id="photoId" type="hidden" name="photo"></td>
            <td><input id="filetypeId" type="hidden" name="filetype"></td>
        </tr>
        <tr>
            <td colspan="2"><img id="headImg" style="width: 200px;height: 200px" alt="还未上传图片"><br></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="提交" style="margin: auto;">
            </td>
        </tr>
    </table>
</form>
</body>
</html>

user.html:实现注册成功后的跳转页面,内容如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册后页面</title>
</head>
<body>
 <h1>注册成功</h1>
</body>
</html>

上述步骤完成后,项目结构如下图所示:

十三、重新部署项目

访问 http://localhost:8080/springboot09/regist 如下:

 输入内容,选择上图图片:

 在图片服务器Tomcat中也可以看到该图片

 点击提交按钮,即可在数据库中看到信息

posted @ 2022-11-22 17:31  酒剑仙*  阅读(1766)  评论(0)    收藏  举报