文件上传下载实战

登录模块

  • springboot JWT 拦截器请求头字段问题

    Authorization
  • axios 传递字段

    axios 传递字段:key: Authorization value: token
  • api 位置问题

    • 修改前

      api 位置问题
    • 修改后

      api 位置
  • 使用axios 拦截器对请求添加token 所需字段

    使用axios 拦截器对请求添加token 所需字段
  • devServer 配置不生效问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    import axios from 'axios'

    let baseURL = '/'
    if (process.env.NODE_ENV === 'production') {
    // 后端接口地址
    baseURL = 'http://localhost:8081/'
    }

    export function request(config) {
    const instance = axios.create({
    baseURL,
    timeout: 5000,
    })
    // 未登录时 token 是空的
    const token = window.localStorage.getItem('token')

    // 请求拦截器
    instance.interceptors.request.use(
    config => {
    // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
    if (token) {
    // 设置统一的 request header Authorization
    config.headers.Authorization = token
    }
    return config
    },
    err => {
    return Promise.reject(err)
    }
    )

    // http response 服务器响应拦截器,这里拦截401错误,并重新跳入登页重新获取token
    instance.interceptors.response.use(
    response => {
    return response
    },
    error => {
    if (error.response) {
    switch (error.response.status) {
    case 401:
    // 这里写清除token的代码
    router.replace({
    path: 'login',
    query: { redirect: router.currentRoute.fullPath }, // 登录成功后跳入浏览的当前页面
    })
    }
    }
    return Promise.reject(error.response.data)
    }
    )
    // 发送真正的网络请求(Promise)
    return instance(config)
    }

  • Login.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    <template>
    <div class="login">
    <el-form
    :model="ruleForm"
    status-icon
    :rules="rules"
    ref="ruleForm"
    label-width="100px"
    class="demo-ruleForm"
    >
    <el-form-item label="用户名" prop="userName">
    <el-input
    v-model="ruleForm.userName"
    type="text"
    autocomplete="off"
    ></el-input>
    </el-form-item>

    <el-form-item label="密码" prop="pass">
    <el-input
    type="password"
    v-model="ruleForm.password"
    autocomplete="off"
    ></el-input>
    </el-form-item>
    <el-form-item label="确认密码" prop="checkPass">
    <el-input
    type="password"
    v-model="ruleForm.checkPass"
    autocomplete="off"
    ></el-input>
    </el-form-item>

    <el-form-item>
    <el-button type="primary" @click="submitForm('ruleForm')">
    登录
    </el-button>
    <el-button type="danger" @click="resetForm('ruleForm')">
    重置
    </el-button>
    </el-form-item>
    </el-form>
    </div>
    </template>

    <script>
    // 后期容易维护
    import { loginApi, testApi } from '@/network/user/login'

    export default {
    data() {
    var validatePass = (rule, value, callback) => {
    if (value === '') {
    callback(new Error('请输入密码'))
    } else {
    if (this.ruleForm.checkPass !== '') {
    this.$refs.ruleForm.validateField('checkPass')
    }
    callback()
    }
    }
    var validatePass2 = (rule, value, callback) => {
    if (value === '') {
    callback(new Error('请再次输入密码'))
    } else if (value !== this.ruleForm.password) {
    callback(new Error('两次输入密码不一致!'))
    } else {
    callback()
    }
    }
    return {
    ruleForm: {
    userName: 'coder-itl',
    password: 'root',
    checkPass: 'root',
    },
    rules: {
    pass: [
    {
    required: true,
    validator: validatePass,
    trigger: 'blur',
    },
    ],
    checkPass: [
    {
    required: true,
    validator: validatePass2,
    trigger: 'blur',
    },
    ],
    userName: [
    {
    required: true,
    message: '请输入用户名',
    trigger: 'blur',
    },
    {
    min: 5,
    max: 15,
    message: '长度在 5 到 15 个字符',
    trigger: 'blur',
    },
    ],
    },
    }
    },
    methods: {
    // 登录提交
    submitForm(formName) {
    this.$refs[formName].validate(async valid => {
    if (valid) {
    let _this = this
    let param = {
    userName: _this.ruleForm.userName,
    password: _this.ruleForm.password,
    }
    // 发起 axios 请求
    const { data: res } = await loginApi(param)
    // 存储用户信息
    window.localStorage.setItem(
    'token',
    // JSON.stringify() 方法将一个 JavaScript 对象或值转换为 JSON 字符串
    res.token
    )
    // 登录成功后发送
    testApi().then(res => {
    console.log(res)
    })

    _this.$router.push('/home')
    } else {
    _this.$message.error('登录失败')
    return false
    }
    })
    },
    // 重置
    resetForm(formName) {
    this.$refs[formName].resetFields()
    },
    },
    }
    </script>

    <style lang="less" scoped>
    .login {
    width: 80%;
    margin: 100px auto;
    padding: 30px;
    box-shadow: 0 0 10px 10px rgba(212, 212, 212, 0.3);
    }
    </style>

  • 后端生成token

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @GetMapping("/login")
    public Map<String, Object> login(User user) {
    log.info("user: {}", user.toString());
    // 数据库用户
    User userDB = userService.login(user);
    Map<String, Object> map = new HashMap<>();
    // 生成 tocken
    Map<String, String> payload = new HashMap<>();
    // token 携带的数据
    payload.put("username", userDB.getUserName());
    payload.put("userId", String.valueOf(userDB.getId()));
    String token = JWTUtils.getToken(payload);
    map.put("state", true);
    map.put("msg", "登录成功!");
    // 响应 token
    map.put("token", token);

    return map;
    }
    • 顾虑

      顾虑
  • 前置路由导航守卫

    1
    2
    3
    4
    5
    6
    7
    8
    9

    router.beforeEach((to, from, next) => {
    if (to.path === '/login') return next()
    //获取token
    const tokenStr = window.localStorage.getItem('token')
    if (!tokenStr) return next('/login')
    next()
    })

文件上传下载需求

  • 要求

    • 用户登录展示用户的所有文件(文件如果是图片则在页面中显示图片)
    • 完成文件的下载和在线打开(注意在线打开不计算为下载次数)
    • 在一张页面中文件文件的上传功能,上传的目录要根据日期每天创建一个文件夹(文件夹统一命名为yyyy-MM-dd),上传完成后要跳转到查询所有页面
  • 项目规范

    • 项目名字统一命名为file_xxx
    • 包名统一com.example.xxx
      • com.example.mapper
      • com.example.service/impl
      • com.example.controller
      • com.example.entity
    • 项目中的webroot
      • 建立files 文件夹作为上传下载的总文件夹(根据日期每天创建不同文件夹,每天上传文件放入到不同文件夹中)
      • 编码格式统一采用UTF-8
  • 数据库表

    • 创建数据库

      1
      create database baizhi_file;
    • 创建表

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      -- 用户信息表
      CREATE TABLE `t_user` (
      `id` int NOT NULL COMMENT '用户id',
      `username` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '用户名',
      `password` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
      PRIMARY KEY (`id`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

      -- 测试数据
      INSERT INTO `baizhi_file`.`t_user` VALUES (1, 'coder-itl', 'coder-itl');
      INSERT INTO `baizhi_file`.`t_user` VALUES (2, 'zhangsan', 'zhangsan');

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      -- 文件信息表
      CREATE TABLE `files`
      (
      `id` int NOT NULL auto_increment,
      `oldFileName` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '原始文件名称',
      `newFileName` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '新文件名称',
      `ext` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '扩展名',
      `path` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件路径',
      `size` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件大小',
      `type` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件类型',
      `isImg` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '是否是图片',
      `downcounts` int DEFAULT NULL COMMENT '下载次数',
      `uploadTime` datetime DEFAULT NULL COMMENT '上传时间',
      `userid` int DEFAULT NULL COMMENT '用户ID',
      PRIMARY KEY (`id`),
      KEY `userid` (`userid`),
      CONSTRAINT `files_user_id` FOREIGN KEY (`userid`) REFERENCES `t_user` (`id`)
      ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8mb4
      COLLATE = utf8mb4_unicode_ci
    • 关系模型

      表关系模型
  • 显示文件列表showAll.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">

    <head>
    <meta charset="UTF-8">
    <title>用户文件列表</title>
    <link rel="stylesheet" href="./myTable.css">
    </head>
    <body>
    <h3>欢迎你: <span th:if="${session.user!=null}" th:text="${session.user.username}"></span></h3>
    <h3>文件列表</h3>
    <table border="1">
    <tr>
    <th>ID</th>
    <th>文件原始名称</th>
    <th>文件新名称</th>
    <th>文件后缀</th>
    <th>存储路径</th>
    <th>文件大小</th>
    <th>类型</th>
    <th>是否是图片</th>
    <th>下载次数</th>
    <th>上传时间</th>
    <th>用户ID</th>
    <th>操作</th>
    </tr>
    <tr th:each="file,fileStat:${fileList}">
    <td><span th:text="${file.id}"></span></td>
    <td><span th:text="${file.oldFileName}"></span></td>
    <td><span th:text="${file.newFileName}"></span></td>
    <td><span th:text="${file.ext}"></span></td>
    <td><span th:text="${file.path}"></span></td>
    <td><span th:text="${file.size}"></span></td>
    <td><span th:text="${file.type}"></span></td>
    <td>
    <img th:if="${file.isImg}=='是'"
    th:src="${#servletContext.contextPath}+${file.path}+'/'+${file.newFileName}"/>
    <span th:if="${file.isImg}!='是'" th:text="${file.isImg}"></span>
    </td>
    <td><span th:text="${file.downcounts}"></span></td>
    <td><span th:text="${file.uploadTime}"></span></td>
    <td><span th:text="${file.userid}"></span></td>
    <td>
    <a th:href="@{/file/download(id=${file.id})}">下载</a>
    <a th:href="@{/file/download(id=${file.id},openStyle='inline')}">在线打开</a>
    <a th:href="@{/file/delete(id=${file.id})}">删除</a>
    </td>
    </tr>

    </table>
    <hr>
    <h3>文件上传</h3>
    <form enctype="multipart/form-data" method="post" th:action="@{/file/upload}">
    上传用户文件: <input name="file" type="file"/><input type="submit" value="上传文件"/>
    </form>
    </body>
    </html>
  • 登录页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
    <meta charset="UTF-8">
    <title>登录</title>
    </head>
    <body>
    <form method="post" th:action="@{/user/login}">
    用户名: <input name="username" type="text"/>
    密码: <input name="password" type="password"/>
    <input type="submit" value="登录">
    </form>
    </body>
    </html>
  • 项目依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.6</version>
    <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>baizhi-file</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>baizhi-file</name>
    <description>baizhi-file</description>
    <properties>
    <java.version>1.8</java.version>
    </properties>
    <dependencies>
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.15</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.0</version>
    </dependency>

    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
    </dependency>

    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
    </dependency>
    </dependencies>

    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
    <excludes>
    <exclude>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </exclude>
    </excludes>
    </configuration>
    </plugin>
    </plugins>
    </build>

    </project>

  • 配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    server:
    port: 8080

    # druid 数据源配置
    spring:
    datasource:
    druid:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/baizhi_file?autoReconnect=true&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true
    username: root
    password: root
    initial-size: 1
    min-idle: 1
    max-active: 20
    # 全局日期输出格式化
    jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    thymeleaf:
    cache: false
    suffix: .html
    prefix: classpath:/templates/
    web:
    resources:
    static-locations: classpath:/templates/, classpath:/static/

    mybatis:
    type-aliases-package: com.example.entity
    mapper-locations: com/example/mapper/*.Mapper
    configuration:
    map-underscore-to-camel-case: true

    logging:
    level:
    com.example.mapper: debug

    upload:
    dir: E:/baizhi-file/target/classes
  • mapper

    • UserMapper.xml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <?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.example.mapper.UserMapper">
      <!-- User login(User user); -->
      <select id="login" parameterType="user" resultType="user">
      select *
      from t_user
      where username = #{username}
      and password = #{password}
      </select>
      </mapper>

    • UserFileMapper.xml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      <?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.example.mapper.UserFileMapper">
      <insert id="save" parameterType="userFile" useGeneratedKeys="true" keyProperty="id">
      INSERT INTO `baizhi_file`.`files` (`oldFileName`, `newFileName`, `ext`, `path`, `size`, `type`, `isImg`,
      `downcounts`, `uploadTime`, `userid`)
      VALUES (#{oldFileName},
      #{newFileName},
      #{ext},
      #{path},
      #{size},
      #{type},
      #{isImg},
      #{downcounts},
      #{uploadTime},
      #{userid});
      </insert>
      <select id="findByUserId" resultType="userFile" parameterType="integer">
      select *
      from files
      where userid = #{uid}
      </select>
      <select id="findById" resultType="userFile" parameterType="int">
      select *
      from files
      where id = #{id}
      </select>
      <update id="update" parameterType="userFile">
      update files
      set downcounts =#{downcounts}
      where id = #{id}
      </update>

      <delete id="delete">
      delete
      from files
      where id = #{id}
      </delete>

      </mapper>

  • service

    • UserService

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      package com.example.service.impl;

      import com.example.entity.User;
      import com.example.mapper.UserMapper;
      import com.example.service.UserService;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Propagation;
      import org.springframework.transaction.annotation.Transactional;

      @Service
      public class UserServiceImpl implements UserService {
      @Autowired
      private UserMapper userMapper;

      @Override
      @Transactional(propagation = Propagation.SUPPORTS)
      public User login(User user) {
      User userInfo = userMapper.login(user);
      return userInfo;
      }
      }

    • UserFileService

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      package com.example.service.impl;

      import com.example.entity.UserFile;
      import com.example.mapper.UserFileMapper;
      import com.example.service.UserFileService;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;

      import java.util.Date;
      import java.util.List;

      @Service
      public class UserFileServiceImpl implements UserFileService {
      @Autowired
      private UserFileMapper userFileMapper;

      @Override
      public List<UserFile> findByUserId(Integer uid) {
      List<UserFile> fileList = userFileMapper.findByUserId(uid);
      return fileList;
      }

      @Override
      public void save(UserFile userFile) {
      // userFile.setIsImg(); 是否是图片
      String isImg = userFile.getType().startsWith("image") ? "是" : "否";
      userFile.setDowncounts(0).setUploadTime(new Date()).setIsImg(isImg);
      userFileMapper.save(userFile);
      }

      @Override
      public UserFile findById(Integer id) {
      UserFile userFile = userFileMapper.findById(id);
      return userFile;
      }

      @Override
      public void update(UserFile userFile) {
      userFileMapper.update(userFile);
      }

      @Override
      public void delete(Integer id) {
      userFileMapper.delete(id);
      }
      }

  • controller

    • UserController

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      package com.example.controller;

      import org.springframework.stereotype.Controller;
      import org.springframework.web.bind.annotation.GetMapping;

      @Controller
      public class IndexController {
      @GetMapping("/index")
      public String toLogin() {
      // return "login"; 指向的是 templates/login.html(thymeleaf在配置文件中添加了相关配置)
      return "login";
      }
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      package com.example.controller;

      import com.example.entity.User;
      import com.example.service.UserService;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Controller;
      import org.springframework.web.bind.annotation.PostMapping;

      import javax.servlet.http.HttpSession;

      @Slf4j
      @Controller
      public class UserController {
      @Autowired
      private UserService userService;

      @PostMapping("/user/login")
      public String login(User user, HttpSession session) {
      User userDB = userService.login(user);
      if (userDB != null) {
      log.info("userDB: {}", userDB);
      session.setAttribute("user", userDB);
      // return "redirect:/file/showAll"; 指向的是一个请求
      return "redirect:/file/showAll";
      } else {
      // 重定向到登录请求
      return "redirect:/index";
      }
      }
      }

    • UserFileController

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      package com.example.controller;

      import com.example.entity.User;
      import com.example.entity.UserFile;
      import com.example.service.UserFileService;
      import lombok.extern.slf4j.Slf4j;
      import org.apache.commons.io.FilenameUtils;
      import org.apache.commons.io.IOUtils;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Controller;
      import org.springframework.ui.Model;
      import org.springframework.util.ResourceUtils;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RequestParam;
      import org.springframework.web.multipart.MultipartFile;

      import javax.servlet.ServletOutputStream;
      import javax.servlet.http.HttpServletResponse;
      import javax.servlet.http.HttpSession;
      import java.io.File;
      import java.io.FileInputStream;
      import java.io.FileNotFoundException;
      import java.io.IOException;
      import java.net.URLEncoder;
      import java.text.SimpleDateFormat;
      import java.util.Date;
      import java.util.List;
      import java.util.UUID;

      @Slf4j
      @Controller
      @RequestMapping("/file")
      public class FileController {
      @Autowired
      private UserFileService userFileService;

      /**
      * 展示所有文件信息
      */
      @GetMapping("/showAll")
      public String showAll(HttpSession session, Model model) {
      User user = (User) session.getAttribute("user");
      Integer userId = user.getId();
      List<UserFile> fileList = userFileService.findByUserId(userId);
      model.addAttribute("fileList", fileList);
      return "showAll";
      }

      /**
      * 上传文件处理 并保存文件信息到数据库中
      */
      @PostMapping("/upload")
      public String upload(@RequestParam("file") MultipartFile file, HttpSession session) throws IOException {
      // 获取登录用户 id
      User user = (User) session.getAttribute("user");
      Integer userId = user.getId();
      // 获取原始文件名称
      String oldFileName = file.getOriginalFilename();
      // 新文件名称 = 原始文件名称 + 生成的新文件名称+后缀(可以手写获取后缀,也可以通过 commons-fileupload 中的工具类(FilenameUtils))
      // 获取文件后缀
      String extension = "." + FilenameUtils.getExtension(oldFileName);
      log.info("extension: {}", extension);
      // 生成新的文件名
      String newFileName = new SimpleDateFormat("yyyyMMdd").format(new Date()) + UUID.randomUUID().toString().replace("-", "") + extension;
      log.info("newFileName: {}", newFileName);
      // 获取文件大小
      long size = file.getSize();
      // 文件类型
      String type = file.getContentType();
      // 根据日期创建目录
      String realPath = ResourceUtils.getURL("classpath:").getPath() + "/static/files";
      String dateFormat = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
      String dateDirPath = realPath + "/" + dateFormat;
      File dateDir = new File(dateDirPath);
      if (!dateDir.exists()) {
      dateDir.mkdirs();
      }
      // 处理文件上传
      file.transferTo(new File(dateDir, newFileName));
      // 将文件信息存入数据库
      UserFile userFile = new UserFile();
      userFile.setOldFileName(oldFileName).setNewFileName(newFileName).setExt(extension).setSize(String.valueOf(size)).setType(type)
      .setPath("/files/" + dateFormat).setUserid(userId);
      userFileService.save(userFile);
      return "redirect:/file/showAll";
      }

      /**
      * 文件下载
      */
      @GetMapping("/download")
      public void download(String openStyle, Integer id, HttpServletResponse response) throws IOException {
      openStyle = openStyle == null ? "attachement" : openStyle;
      // 获取文件信息
      UserFile userFile = userFileService.findById(id);
      // 只有当点击下载链接并且是附件的形式是下载次数更新
      if ("attachement".equals(openStyle)) {
      // 更新下载次数
      userFile.setDowncounts(userFile.getDowncounts() + 1);
      userFileService.update(userFile);
      }
      // 根据文件信息中 文件名字 和 文件存储路径获取文件输入流
      String realPath = ResourceUtils.getURL("classpath:").getPath() + "/static" + userFile.getPath();
      // 获取文件输入流
      FileInputStream is = new FileInputStream(new File(realPath, userFile.getNewFileName()));
      // 附件下载
      response.setHeader("content-disposition", openStyle + ";fileName=" + URLEncoder.encode(userFile.getOldFileName(), "UTF-8"));
      // 获取响应输出流
      ServletOutputStream os = response.getOutputStream();
      // 文件拷贝
      IOUtils.copy(is, os);
      IOUtils.closeQuietly(is);
      IOUtils.closeQuietly(os);
      }

      /**
      * 文件删除: delete
      */
      @GetMapping("/delete")
      public String delete(Integer id) throws FileNotFoundException {
      // 根据 id 查询信息
      UserFile userFile = userFileService.findById(id);
      // 删除物理文件
      String realPath = ResourceUtils.getURL("classpath:").getPath() + "/static" + userFile.getPath();
      File file = new File(realPath, userFile.getNewFileName());
      if (file.exists()) {
      // 立即删除
      file.delete();
      }
      // 删除数据库中的记录信息
      userFileService.delete(id);
      // 回到当前页面
      return "redirect:/file/showAll";
      }
      }

  • 整体效果

    文件上传下载

Table-美化

  • css

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    table {
    border-collapse: collapse;
    width: 100%;
    border: 1px solid #c6c6c6 !important;
    margin-bottom: 20px;
    }

    th {
    border-collapse: collapse;
    border-right: 1px solid #c6c6c6 !important;
    border-bottom: 1px solid #c6c6c6 !important;
    background-color: #ddeeff !important;
    padding: 5px 9px;
    font-size: 14px;
    font-weight: normal;
    text-align: center;
    }

    td {
    border-collapse: collapse;
    border-right: 1px solid #c6c6c6 !important;
    border-bottom: 1px solid #c6c6c6 !important;
    padding: 5px 9px;
    font-size: 12px;
    font-weight: normal;
    text-align: center;
    word-break: break-all;
    }

    table tr:nth-child(odd) {
    background-color: #fff !important;
    }

    span {
    color: darkorange;
    }

    td img {
    width: 100px;
    height: 10%;
    }

    table tr:nth-child(even) {
    background-color: #f8f8f8 !important;
    }

commons-fileupload

  • 处理下载流复制

    1
    2
    3
    4
    // 文件拷贝
    IOUtils.copy(is, os);
    IOUtils.closeQuietly(is);
    IOUtils.closeQuietly(os);
    • 解决的问题

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // 传统写法
      int len;
      byte[] b = new byte[1024];
      while (true){
      len = is.read();
      if(len==-1) break;
      os.write(b,0,len);
      }
      // 释放资源
      is.close();
      os.close();

      IO 使用IOUtils,文件操作使用FileUtils