SpringBoot+VUE实现文件导入并将其保存到Liunx系统

一、需求

  • 必须支持PDF、docx、xlsx、xls、doc等格式
  • 文件上传后保存在本地文件夹
  • 需要进行在线预览

二、前端代码实现

2.1 显示实现

首先我们需要添加一个用于操作的按钮上去,像这样的:

<a @click="fileRef.onOpen(record)">查看文件</a>
<a @click="ProfessionImpExpRef.onOpen(record)">上传文件</a>

2.1.1 a标签实现

2.1.1.1 上传标签实现

当用户点击上传文件时我们会在右侧打开一个抽屉,用以展示上传界面,页面部分像这样:

<ProfessionImpExp ref="ProfessionImpExpRef" />

具体逻辑像这样:

//professionImpExp.vue 为上传界面
import ProfessionImpExp from './professionImpExp.vue'
const ProfessionImpExpRef = ref()

2.1.1.2 查看标签实现

文件查看部分基本和上传部分实现类似,页面部分:

<File ref="fileRef" @successful1="table.refresh(true)" />
//file.vue为文件查看界面
import File from "./file.vue";
const fileRef = ref()
const file = ref(null)

2.2 上传文件和文件查看界面实现

2.2.1 上传文件界面

2.2.1.1 上传文件界面展示部分

当用户点击上传文件时,我们需要打开一个界面用以提示用户支持的类型和上传注意事项、上传结果,像这样的:

<template>
	<xn-form-container title="导入导出" :width="700" :visible="visible" :destroy-on-close="true" @close="onClose">
		<span
			>导入数据格式严格按照要求进行数据录入,<b style="color: red">重复导入或使用重名文件将会覆盖之前数据内容</b>
		</span>
		<a-divider dashed />
		<div>
			<a-spin :spinning="impUploadLoading">
				<a-upload-dragger :show-upload-list="false" :custom-request="customRequestLocal" :accept="uploadAccept">
					<p class="ant-upload-drag-icon">
						<inbox-outlined></inbox-outlined>
					</p>
					<p class="ant-upload-text">单击或拖动文件到此区域进行上传</p>
					<p class="ant-upload-hint">仅支持PDF、docx、xlsx、xls、doc格式文件</p>
				</a-upload-dragger>
			</a-spin>
		</div>
		<a-alert v-if="impAlertStatus" type="info" :show-icon="false" banner closable @close="onImpClose" class="mt-3">
			<template #description>
				<p>导入完成</p>
			</template>
		</a-alert>
	</xn-form-container>
</template>

展示出来就是下面的效果:
在这里插入图片描述

2.2.1.1 上传文件界面逻辑部分

用户点击后,我们会打开一个抽屉,并将获取到的本行数据id传入到这个界面和上传的数据文件进行绑定,之后传回后端处理。

import {
    
     message } from 'ant-design-vue'
	import professionApi from '@/api/biz/professionApi'
	import {
    
    cloneDeep} from "lodash-es";
	const impUploadLoading = ref(false)
	const impAlertStatus = ref(false)
	const dataId = ref()
	const impAccept = [
		{
    
    
			extension: '.xls',
			mimeType: 'application/vnd.ms-excel'
		},
		{
    
    
			extension: '.xlsx',
			mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
		},
		{
    
    
			extension: '.PDF',
			mimeType: 'application/pdf'
		},
		{
    
    
			extension: '.docx',
			mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
		},
		{
    
    
			extension: '.doc',
			mimeType: 'application/msword'
		},
	]
	// 指定能选择的文件类型
	const uploadAccept = String(
		impAccept.map((item) => {
    
    
			return item.mimeType
		})
	)
	// 导入
	const customRequestLocal = (data) => {
    
    
		impUploadLoading.value = true
		// 校验上传文件扩展名和文件类型是否为支持类型
		const extension = '.'.concat(data.file.name.split('.').slice(-1).toString().toLowerCase())
		const mimeType = data.file.type
		// 提取允许的扩展名
		const extensionArr = impAccept.map((item) => item.extension)
		// 提取允许的MIMEType
		const mimeTypeArr = impAccept.map((item) => item.mimeType)
		if (!extensionArr.includes(extension) || !mimeTypeArr.includes(mimeType)) {
    
    
			message.warning('上传文件类型仅支持PDF、word、excel、xls、xlsx格式文件!')
			impUploadLoading.value = false
			return false
		}

		const formData = new FormData();
		formData.append("file",data.file)
		formData.append("id",dataId.value.id)
		return professionApi
			.professionImport(formData)
			.then((res) => {
    
    
				impAlertStatus.value = res
			})
			.finally(() => {
    
    
				impUploadLoading.value = false
			})
	}
	// 关闭导入提示
	const onImpClose = () => {
    
    
		impAlertStatus.value = false
	}
	// 定义emit事件
	const emit = defineEmits({
    
     successful: null })
	// 默认是关闭状态
	let visible = ref(false)
	const submitLoading = ref(false)

	// 打开抽屉
	const onOpen = (record) => {
    
    
		visible.value = true
		if (record) {
    
    
			let recordData = cloneDeep(record)
			dataId.value = Object.assign({
    
    }, recordData)
		}
	}
	// 关闭抽屉
	const onClose = () => {
    
    
		visible.value = false
		// 关闭导入的提示
		onImpClose()
	}

	// 调用这个函数将子组件的一些数据和方法暴露出去
	defineExpose({
    
    
		onOpen
	})

2.2.2 查看文件界面

2.2.2.1 查看文件界面展示部分

用户点击查看文件后,进入到这个界面进行文件查看和在线展示。

<template>
	<xn-form-container
		:title="'文件详情'"
		:width="500"
		:visible="visible"
		:destroy-on-close="true"
		@close="onClose"
	>
		<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
			<a-form-item label="文件:" name="url">
				<ol>
					<li v-for="data in formData">
						<a :href="'http://view.officeapps.live.com/op/view.aspx?src=https://img.qcybj.com/file/'+data" target="_blank"> {
   
   { data }}</a>
						
					</li>
				</ol>
			</a-form-item>
		</a-form>
		<template #footer>
			<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
		</template>
	</xn-form-container>
</template>

具体的效果类似这种:
在这里插入图片描述

2.2.2.2 查看文件界面逻辑部分

这部分的逻辑很简单,主要就是对后端的数据解析并绑定。

import {
    
     cloneDeep } from 'lodash-es'
import XnFormContainer from "@/components/XnFormContainer/index.vue";

// 抽屉状态
const visible = ref(false)
const emit = defineEmits({
    
     successful: null })
const file = ref()
const submitLoading = ref(false)
const formData = ref({
    
    })
// 打开抽屉
const onOpen = (record) => {
    
    
	visible.value = true
	if (record) {
    
    
		let recordData = cloneDeep(record)
		let str = Object.assign({
    
    }, recordData).url.substr(1)
		let str1 = str.substring(0,str.length-1)
		formData.value = str1.split(",")
	}
}
// 关闭抽屉
const onClose = () => {
    
    
	visible.value = false
}
// 默认要校验的
const formRules = {
    
    
}
// 抛出函数
defineExpose({
    
    
	onOpen
})

2.2 其他逻辑

2.2.1 本行数据id选择逻辑

const selectedRowKeys = ref([])
	// 列表选择配置
	const options = {
    
    
		// columns数字类型字段加入 needTotal: true 可以勾选自动算账
		alert: {
    
    
			show: true,
			clear: () => {
    
    
				selectedRowKeys.value = ref([])
			}
		},
		rowSelection: {
    
    
			onChange: (selectedRowKey, selectedRows) => {
    
    
				selectedRowKeys.value = selectedRowKey
			}
		}
	}

2.2.2 API 逻辑

这部分内容可以参照我之前的文章,这里不再多说~

	//业务导入
	professionImport(data) {
    
    
		return request('import', data)
	},

三、后端实现

后端部分大多分逻辑SpringBoot + Ant Design Vue实现数据导出功能都有提及,这里不再多言。我们之说最重要的实现逻辑:

  @Transactional(rollbackFor = Exception.class)
    @Override
    public String importProfession(MultipartFile file,String id ) throws IOException {
    
    
        Profession profession = baseMapper.selectById(id);
        String urlList = profession.getUrl();
        //定义文件名
        String imgName = file.getOriginalFilename();
        //定义上传路径
        String upPath = path + imgName;
        byte[] bytes = new byte[1024];
        int dataLine;
        try(BufferedInputStream bufferedInputStream = new BufferedInputStream(file.getInputStream());
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(upPath))){
    
    
            while ((dataLine=bufferedInputStream.read(bytes))!= -1){
    
    
                bufferedOutputStream.write(bytes, 0, dataLine);
            }
        }
        if(urlList == null){
    
    
            profession.setUrl(imgName);
        }else {
    
    
            List<String> list = Arrays.asList(urlList.split(","));
            List<String> arrList = new ArrayList<>(list);
            arrList.add(imgName);
            profession.setUrl(arrList.toString());
        }
        QueryWrapper<Profession> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(Profession::getId,id);
        this.update(profession,queryWrapper);
        return imgName;
    }

这里有一点需要注意,Java的IO方法很多,需要注意的是liunx系统中写入要指定绝对路径,不要用相对路径。

比如 MultipartFile 的 transferTo() 方法就可能使用相对路径。一旦使用了相对路径你就会发现原本指定写入的路径前多出了它:

/tmp/tomcat.8080.450079304707479782/work/Tomcat/localhost/ROOT

猜你喜欢

转载自blog.csdn.net/qq_35241329/article/details/131425113