Springboot+vue2 文件上传下载+pdf预览

8 篇文章 0 订阅
2 篇文章 0 订阅

项目场景:

后端:文件上传下载接口
前端:element ui +vue 图片/pdf预览


直接上代码

配置application.yml,方便后续对端口 ip进行修改

server:
  port: 8080
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://${ip}:3306/vue-books-project?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    username: root
    password: root

ip: localhost

配置拦截,如果在方法或者类上有@AuthAccess 就进行放行,如预览上不需要拦截

import java.lang.annotation.*;

/*
* 1.@Target(ElementType.METHOD):这个注解表示@AuthAccess使用目标是方法Method,@AuthAccess只能用于方法上
* 2.@Retention(RetentionPolicy.RUNTIME) 注解保留策略是RUNTIME.意味着在运行时可以通过反射来获取@AuthAccess注解的信息
* 3.@Documented:这个注解表示 @AuthAccess 注解应该被包含在 Javadoc 中。这有助于生成文档时保留注解信息。
* */
//这行代码定义了一个名为 AuthAccess 的自定义注解。注解本质上是一个特殊的接口。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthAccess {
}

拦截类

import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.example.qinggedemoone.entity.User;
import com.example.qinggedemoone.exception.ServiceException;
import com.example.qinggedemoone.mapper.UserMapper;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

//以拦截器方式进行继承
public class JwtInterceptor implements HandlerInterceptor {

    @Resource
    private UserMapper userMapper;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token=request.getHeader("token"); //前端header中传过来的参数
        if(StrUtil.isBlank(token)){
            token=request.getParameter("token");   //url参数   ?token=xxxx
        }

        //只要方法上有@AuthAccess就放行的意思
        if(handler instanceof HandlerMethod){
            AuthAccess annotation = ((HandlerMethod) handler).getMethodAnnotation(AuthAccess.class);
            if(annotation!=null){
                return true;
            }
        }

        System.out.println("==token==:"+token);
        //执行认证
        if(StrUtil.isBlank(token)){
            throw new ServiceException("401","请登录");
        }
        //获取token中的user id
        String userId;
        try{
            //getAudience相当于一个仓库,存储了很多信息
            //get(0) 获取audience中的第一个数据
            userId= JWT.decode(token).getAudience().get(0);
        }catch (JWTDecodeException j){
            throw new ServiceException("401","请登录");
        }
        //更具token中的userId查询数据
        User user = userMapper.selectById(userId);
        System.out.println("userId:"+userId);
        System.out.println("++user++"+user);
        if(user==null){
            throw new ServiceException("401","请登录");
        }
        //通过用户密码  加密  生成一个验证器
        /*
        * 使用用户密码(user.getPassword())作为密钥来创建一个 HMAC256 算法实例,
        * 并基于这个算法实例生成一个 JWT 验证器(JWTVerifier)。JWT.require(Algorithm)
        * 方法返回一个 JWT 构建器,通过调用 .build() 方法来构建验证器。
        * Algorithm:算法
        * */
        JWTVerifier build = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
        try{
            build.verify(token);   //进行校验
        }catch (JWTVerificationException e){
            throw new ServiceException("401","请登录");
        }
        return true;
    }
}

拦截规则

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/*
* 步骤
* 1.编写JwtInterceptor生成规则
* 2.编写InterceptorConfig增加规则,进行拦截
* 3.编写TokenUtils 生成token返回给前端
* */

/*
* InterceptorConfig拦截
* JwtInterceptor定义拦截规则
* */


/*@Configuration配置类*/
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {

    /*
    * 实现addInterceptors方法  addInterceptor增加拦截器规则
    * addPathPatterns:拦截路径   /**拦截所有的请求路径
    * */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(jwtInterceptor())
                .addPathPatterns("/**").excludePathPatterns("/login","/register");

        super.addInterceptors(registry);
    }

    /*
        @Bean注入到容器中
    */
    @Bean
    public JwtInterceptor jwtInterceptor(){
        return new JwtInterceptor();
    }
}

后端接口

@RestController
@RequestMapping("/file")
public class FileController {
    @Value("${ip:localhost}")   //读取application.yml中的变量
    String ip;
    @Value("${server.port}")
    String port;
    //D:\JAVA\QingGeDemoOne\files
    private static final String ROOT_PATH=System.getProperty("user.dir")+File.separator+"files";
    //上传接口
    //@AuthAccess
    @PostMapping("/upload")
    public Result upload(MultipartFile file) throws IOException {
        String originalFilename = file.getOriginalFilename();//文件的原始名称(包含文件类型和文件名)
        String mainName = FileUtil.mainName(originalFilename);    //取到文件的名称 不包含后缀的一个文件名称
        String extName = FileUtil.extName(originalFilename);//获取文件的后缀名

        if(!FileUtil.exist(ROOT_PATH)){
            FileUtil.mkdir(ROOT_PATH);  //如果当前文件的父级目录不存在,就创建
        }
        //D:\JAVA\QingGeDemoOne\files\aaa.png
        if(FileUtil.exist(ROOT_PATH+File.separator+originalFilename)){   //如果当前上传的文件已经存在了,那么这个时候就要重命名一个文件名称
            originalFilename=System.currentTimeMillis()+ mainName+"."+extName;
        }
        File saveFile=new File(ROOT_PATH+File.separator+originalFilename);  //File.separator:文件分隔符  /
        file.transferTo(saveFile);  //存储文件到本地的磁盘中
        String url="http://"+ip+":"+port+"/file/download/"+originalFilename;
        return Result.success(url);
    }
    //下载接口
    @AuthAccess
    @GetMapping("/download/{fileName}")
    public void download(@PathVariable String fileName, HttpServletResponse response) throws IOException {
        String filePath=ROOT_PATH + File.separator + fileName;
        if(!FileUtil.exist(filePath)){
            return;
        }
        byte[] bytes = FileUtil.readBytes(filePath);
        ServletOutputStream outputStream = response.getOutputStream();
        outputStream.write(bytes);   //数组是一个字节数组,也就是文件的字节流数组
        outputStream.flush();   //刷新文件流
        outputStream.close();  //关闭文件流
    }
}

前端:使用el-upload组件

<template>
  <div>
    <el-container>
      <!--侧边栏-->
      <el-aside :width="asideWidth" style="min-height: 100vh; background-color:#001529;">
        <div style="height: 60px; display: flex;  justify-content:center;align-items:center;color: white;">
          <img src="@/assets/96.png" style="width: 40px;">
          <span class="log-title" v-show="!isCollapse">honey 2024</span>
        </div>
        <!--加上 router关键字 就可以实现跳转了-->
        <el-menu :collapse="isCollapse" :collapse-transition="false" router background-color="#001529"
          text-color="rgba(255,255,255,0.65)" active-text-color="#fff" style="border:none;"
          :default-active="$route.path"> <!--route.path 当前浏览器正在访问的路由-->
          <el-menu-item index="/"> <!--只有el-menu-item 才能生效  index需要与router index.js匹配-->
            <i class="el-icon-house"></i>
            <span slot="title">系统首页</span>
          </el-menu-item>
          <el-menu-item index="/element">element页面</el-menu-item>
          <el-menu-item>系统首页</el-menu-item>
          <el-menu-item>系统首页</el-menu-item>
          <el-submenu index="">
            <template slot="title">
              <i class="el-icon-menu"></i>
              <span>信息管理</span>
            </template>
            <el-menu-item>用户信息</el-menu-item>
            <el-menu-item>学生信息</el-menu-item>
          </el-submenu>
        </el-menu>
      </el-aside>

      <el-container>
        <!--头部区域-->
        <el-header>
          <i :class="collapseIcon" style="font-size: 25px;" @click="handleCollapse"></i>
          <el-breadcrumb separator-class="el-icon-arrow-right" style="margin-left: 20px;">
            <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
            <el-breadcrumb-item :to="{ path: '/element' }">用户管理</el-breadcrumb-item>
          </el-breadcrumb>

          <div style="flex: 1;width: 0; display: flex; align-items: center;justify-content: flex-end;">
            <i class="el-icon-quanping_o" style="font-size: 35px;" @click="handleful"></i>
            <el-dropdown placement="bottom">
              <div style="display: flex; align-items: center; cursor: default;">
                <img src="@/assets/96.png" style="width:40px;height: 40px;margin-left: 10px;">
                <span>管理员</span>
              </div>
              <el-dropdown-menu slot="dropdown">
                <el-dropdown-item>个人信息</el-dropdown-item>
                <el-dropdown-item>修改密码</el-dropdown-item>
                <el-dropdown-item @click.native="logout">退出登录</el-dropdown-item>
              </el-dropdown-menu>
            </el-dropdown>
          </div>


        </el-header>
        <!--主体-->
        <el-main>
          <div style="box-shadow: 0 0 10px rgba(0,0,0,.1);padding: 10px 20px;border-radius: 5px;margin-bottom: 10px;">
            早安 少年 开心每一天
          </div>

          <div class="myclass" style="display: flex;">
            <div>
              <el-card style="width:500px">
                <div slot="header" class="clearfix">
                  <span>做练习2024</span>
                </div>
                <div>
                  多练习,多进步
                  <div style="margin-top: 20px;">
                    <div style="margin: 10px 0;"><strong>主题色</strong></div>
                    <el-button type="primary" size="default" @click="getListUser">按钮</el-button>
                    <el-button type="success" size="default">按钮</el-button>
                    <el-button type="warning" size="default">按钮</el-button>
                    <el-button type="danger" size="default">按钮</el-button>
                    <el-button type="info" size="default">按钮</el-button>
                  </div>
                </div>
              </el-card>
            </div>
            <div style="margin-left: 20px;">
              <div style="display: flex;">
                这是另一块区域,我厉害吧
              </div>
            </div>
          </div>

          <div style="margin-top: 5px;padding-top: 20px;">
            <span></span>
          </div>

          <el-table :data="tableData" style="width: 100%;" class="center-table">

            <el-table-column type="index" label="序号" width="180" align="center">
            </el-table-column>

            <el-table-column prop="username" label="姓名" width="180" align="center">
            </el-table-column>
            <el-table-column prop="phone" label="电话" width="180" align="center">
            </el-table-column>
            <el-table-column prop="email" label="邮箱" align="center">
            </el-table-column>
            <el-table-column prop="address" label="地址" align="center">
            </el-table-column>
            <el-table-column style="display: flex;" label="操作" align="center" width="200px">
              <template slot-scope="scope">
                <div style="display: flex; justify-content: space-around;">
                  <el-button type="mini" size="primary" @click="DelUser(scope.row)">删除</el-button>
                  <el-upload class="upload-demo" action="http://localhost:8080/file/upload/"
                    accept=".jpg,.jpeg,.png,.gif,.pdf" :show-file-list="false" :headers="{ token: user.token }"
                    :on-success="(row, file, fileList) => handleTableFileUpload(scope.row, file, fileList)">
                    <!--()=>xx  代表成功后才调用这个函数   如果没有=》代表直接调用这个函数-->
                    <!--row是行对象-->
                    <el-button size="mini" type="primary">点击上传</el-button>
                  </el-upload>
                </div>
              </template>
            </el-table-column>

            <el-table-column label="头像" align="center">
              <template v-slot="scope">
                <el-image v-if="scope.row.avatar" :src="scope.row.avatar" style="width: 50px;height: 50px;"></el-image>
                <div><el-button @click="perview(scope.row.avatar)">预览</el-button></div>
              </template>
            </el-table-column>


          </el-table>

          <div style="display: flex; margin: 10px 0;">
            <el-card style="width: 50%; margin-right: 10px;">
              <div slot="header" class="clearfix">
                <span style="display: block;text-align: left;">文件上传下载</span>
              </div>
              <div>
                <el-upload class="upload-demo" action="http://localhost:8080/file/upload/" accept=".jpg,.jpeg,.png,.gif"
                  :headers="{ token: user.token }" :on-success="handleFileUpload">
                  <el-button size="small" type="primary">点击上传</el-button>
                  <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
                </el-upload>
              </div>
            </el-card>
          </div>

          <div>
            <input v-model="pdfId" placeholder="Enter PDF ID" />
            <button @click="viewPdf">View PDF</button>
          </div>

        </el-main>
      </el-container>
    </el-container>
  </div>
</template>

<script>
import { getListApi } from '@/api/cs'
import request from '@/utils/request'
export default {
  data() {
    return {
      i: 0,
      isCollapse: false,  //不收缩
      asideWidth: '200px',
      collapseIcon: 'el-icon-s-fold',
      tableData: [],
      user: JSON.parse(localStorage.getItem('honey-user') || '{}'),
      pdfId:'RESTING_001112312312320230905100637778.pdf'
    }
  },
  methods: {
    perview(url){
      window.open(url)
    },
    viewPdf() {
      if (this.pdfId) {
        const url = `http://localhost:8080/file/download/${this.pdfId}`;
        window.open(url, '_blank');
      } else {
        alert('Please enter a PDF ID');
      }
    },
    async getListUser() {
      request.get('/userApi/getList').then(res => {
        this.tableData = res.data
      })
    },
    handleTableFileUpload(row, file, fileList) {
      console.log(row, file, fileList)
      row.avatar = file.response.data  //res.data是一个连接: http://localhost:8080/file/download/123.jpg
      //更新操作
      request.put('/userApi/UpdateUser', row).then(res => {
        if (res.code === '200') {
          this.$message.success('上传成功')
          this.getListUser();
        } else {
          this.$message.success('上传失败')
        }
      })
    },
    async DelUser(row) {
      request.delete(`/userApi/delUser/${row.id}`).then(response => {
        console.log(`选中的用户ID为 ${row.id} 删除成功.`);
        // 在这里更新你的UI,例如删除相应的DOM元素
        this.getListUser();
      })
        .catch(error => {
          console.error(`Error deleting todo item with ID ${row.id}: ${error}`);
        });
    },
    handleFileUpload(response, file, fileList) {
      console.log('=========')
      console.log(this.fileList)
      console.log(response, file, fileList)
    },
    logout() {
      alert('123')
      localStorage.removeItem('honey-user')   //清除当前token和用户数据
      this.$router.push('/login')
    },
    handleCollapse() {
      this.isCollapse = !this.isCollapse,
        this.asideWidth = this.isCollapse ? '64px' : '200px'
      this.collapseIcon = this.isCollapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'
    },
    handleful() {
      document.documentElement.requestFullscreen()   //全屏
    }

  },
  mounted() {
    request.get('/userApi/getList').then(res => {
      this.tableData = res.data
    })
  }
}
</script>

<style>
.el-submenu .el-menu-item {
  background-color: #000c17 !important;
  /**!important:强制生效 */
}

.el-menu-item:hover,
.el-submenu__title:hover {
  color: #fff !important;
}

.el-menu-item.is-active {
  background-color: #1890ff !important;
  border-radius: 4px !important;
  margin: 4px !important;
}

.el-menu-item {
  height: 40px !important;
  line-height: 40px !important;
  margin: 4px !important;
}

.el-submenu__title {
  height: 40px !important;
  line-height: 40px !important;
  margin: 4px !important;
}

.el-menu--inline {
  background-color: #000c17 !important;
}

.log-title {
  margin-left: 5px;
  transition: all .10s;
}

.el-header {
  box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
  display: flex;
  align-items: center;
}


:deep(.center-table td),
:deep(.center-table th) {
  text-align: center !important;
}
</style>

使用postman测试
在这里插入图片描述

效果图
在这里插入图片描述


  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring BootVue项目中实现PDF在线预览可以通过集成pdf.js库来实现。首先,您需要从官网下载pdf.js源码,并将其放入您的项目中。具体步骤如下: 1. 在官方网站下载pdf.js源码。 2. 将下载的源码放入您的Spring Boot项目中的某个目录中。 3. 打开viewer.html文件,您会注意到它会自动跳转到一个pdf文件。这个pdf文件是官方的示例文件。 4. 如果您只想打开一个特定的pdf文件,只需将官方示例文件替换为您自己的pdf文件。 5. 在viewer.js文件中搜索"defaultUrl",大约在第3500行左右。您可以找到类似上述代码的部分(注意:不同版本的pdf.js可能会略有差异)。 6. 只需更改"value"的值,即可实现预览您指定的pdf文件。 7. 如果您需要根据前端传递的不同值来响应不同的pdf文件,可以使用动态获取的方式来实现。 另外,如果希望使用浏览器自带的预览pdf功能,可以参考使用pdf.js的实现方式。但需要注意的是,某些浏览器可能不支持此功能,如360浏览器。您可以在我的博客中找到有关使用浏览器预览pdf的更多信息。 综上所述,您可以根据以上步骤在Spring BootVue项目中实现PDF在线预览。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [springboot+vue整合pdf.js实现预览pdf](https://blog.csdn.net/qq_14853853/article/details/111176085)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值