SpringBoot+VUE implements file import and saves it to the Liunx system

1. Demand

  • Must support PDF, docx, xlsx, xls, doc and other formats
  • After the file is uploaded, it is saved in a local folder
  • online preview required

2. Front-end code implementation

2.1 Display implementation

First we need to add a button for operation, like this:

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

2.1.1 a tag implementation

2.1.1.1 Upload tag implementation

When the user clicks to upload a file, we will open a drawer on the right to display the upload interface. The page part looks like this:

<ProfessionImpExp ref="ProfessionImpExpRef" />

The specific logic is like this:

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

2.1.1.2 View tag implementation

The file viewing part is basically similar to the upload part, and the page part:

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

2.2 Upload file and file view interface implementation

2.2.1 Upload file interface

2.2.1.1 Upload file interface display part

When the user clicks to upload a file, we need to open an interface to prompt the user for supported types, upload precautions, and upload results, like this:

<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>

The display is the following effect:
insert image description here

2.2.1.1 Logical part of upload file interface

After the user clicks, we will open a drawer, and pass the acquired data id of the bank to this interface to bind with the uploaded data file, and then send it back to the backend for processing.

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 View file interface

2.2.2.1 View file interface display part

After the user clicks to view the file, he enters this interface for file viewing and online display.

<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>

The specific effect is similar to this:
insert image description here

2.2.2.2 View the logic part of the file interface

The logic of this part is very simple, mainly parsing and binding the back-end data.

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 Other logic

2.2.1 The row data id selection logic

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 logic

This part of the content can refer to my previous article, so I won’t say more here~

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

3. Backend implementation

Most of the backend part is divided into logical SpringBoot + Ant Design Vue to realize the data export function , so I won’t say more here. We say the most important implementation logic:

  @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;
    }

One thing to note here is that there are many IO methods in Java. It should be noted that in the liunx system, you must specify an absolute path instead of a relative path.

For example, the transferTo() method of MultipartFile may use a relative path. Once you use a relative path, you will find that it is added before the path that was originally specified:

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

Guess you like

Origin blog.csdn.net/qq_35241329/article/details/131425113