若依实现excel表内数据批量导入

注意:此文章展示功能基本实现,但有bug,基本逻辑对,细节需要自己进行一下处理(用于学习理解这个过程即可)

文件上传组件 - el-upload

<el-upload
    class="upload-demo"
    drag
    action="uploadUrl"
    :headers="header"
    multiple:true
    accept=".xls,.xlsx"
    :before-upload="beforeUpload"
    :on-success="handleUploadSuccess"
    :http-request="customUploadRequest"
    :on-progress="handleUploadProgress"
>
    <el-row :span="24">
        <el-col :span="6">
            <el-icon style="margin-top: 25px" class="el-icon--upload"><upload-filled /></el-icon>
        </el-col>
        <el-col :span="18">
            <p style="font-weight: 700;font-size: 20px">上传填好的数据表</p>
            <div class="el-upload__text">
                将文件拖到此处或者点击<em>上传文件</em>
                <div class="el-upload__tip">
                    文件后缀名必须是 xls 或 xlsx,文件大小不超过 500KB
                </div>
            </div>
        </el-col>
    </el-row>
</el-upload>
  • 功能与属性
    • drag:支持文件拖拽上传,提供便捷操作。
    • action:指定后端上传接口地址,由 uploadUrl 计算属性确定。
    • headers:设置请求头,确保后端正确解析文件格式。
    • multiple:true:允许批量上传多个 Excel 文件。
    • accept:限制只能选择 .xls 或 .xlsx 文件,保证文件类型正确。

(二)文件上传前验证 - beforeUpload

const beforeUpload = (file) => {
    const isLxls = file.name.endsWith('.xls') || file.name.endsWith('.xlsx');
    const isSizeValid = file.size / 1024 / 1024 < 0.5;  // 文件大小转换为 MB 并与 500KB(0.5MB)比较
    if (!isLxls) {
        proxy.$message.error('请上传后缀名为.xls或.xlsx的文件,当前文件格式不符合要求');
        return false;
    }
    if (!isSizeValid) {
        proxy.$message.error('文件大小超过了 500KB 限制,请重新选择文件,当前文件大小为:' + (file.size / 1024 / 1024).toFixed(2) +'MB');
        return false;
    }
    return true;
};
  • 验证逻辑
    • 检查文件后缀名,不符合则提示错误并阻止上传。
    • 验证文件大小,超限制则报错并阻止上传,确保上传文件符合要求。

 (三)文件上传成功回调 - handleUploadSuccess

const handleUploadSuccess = (response, file, fileList) => {
    proxy.$message.success('文件上传成功');
    // 将上传成功的文件信息添加到 uploadedFiles 数组中,这里简单记录文件名,可根据需求扩展记录更多信息
    uploadedFiles.value.push({ name: file.name });
};
  • 处理流程
    • 显示上传成功提示。
    • 将文件名记录到 uploadedFiles 数组,用于后续操作或展示。

 (四)自定义上传请求 - customUploadRequest

const customUploadRequest = (option) => {
    const formData = new FormData();
    formData.append('file', option.file);
    axios({
        method: 'post',
        url: uploadUrl.value,
        data: formData,
        headers:header,
        onUploadProgress: (progressEvent) => {
            if (progressEvent.lengthComputable) {
                const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
                uploadProgress.value = percent;
            }
        }
    }).then(response => {
        option.onSuccess(response.data, option.file, option.fileList);
    }).catch(error => {
        option.onError(error);
    });
};
  • 请求定制
    • 构建 FormData 封装文件,按 POST 方式发送请求。
    • 监听上传进度,实时更新 uploadProgress,展示上传状态。
    • 请求成功或失败时,分别调用对应回调函数处理结果。

 (五)上传进度更新 - handleUploadProgress

 

const handleUploadProgress = (event, file, fileList) => {
    if (event.percent) {
        uploadProgress.value = Math.round(event.percent);
    }
};
  • 进度显示:根据上传事件中的进度信息,更新 uploadProgress 值,实时显示上传进度条。

 (六)数据添加情况检查 - checkUploadedData 与 handleNextStep

 

const checkUploadedData = () => {
  return axios({
    method: 'post',
    url: 'http://localhost:8083/system/upload/checkUploadedData',
    headers: {"Content-Type": "application/json"},
    // 明确设置响应类型为 JSON,确保正确解析后端返回的 JSON 数据
    responseType: 'json'
  }).then(response => {
    return response.data;  // 返回解析后的 JSON 数据主体部分,以便后续判断使用
  }).catch(error => {
    proxy.$message.error('请求确认数据添加情况时出现错误:' + error.message);
    return null;
  });
};

const handleNextStep = () => {
  checkUploadedData().then(response => {
    console.log("response为", response);
    if (typeof response === 'object' && response!== null && response.code === 200) {
      proxy.$message.success('数据已成功添加到数据库');
    } else {
      proxy.$message.error('数据添加到数据库出现问题,请检查');
    }
  }).catch(error => {
    proxy.$message.error('检查数据添加情况时出现错误:' + error.message);
  });
};
  • 数据检查逻辑
    • checkUploadedData:向后端发送请求检查上传数据是否成功添加到数据库,设置请求头和响应类型,处理成功与失败情况。
    • handleNextStep:调用 checkUploadedData,根据返回结果判断并向用户提示数据添加状态,方便用户知晓操作结果。

 后端接口

文件上传接口 - ExcelUploadController

 (一)文件上传接口 - ExcelUploadController 中的 uploadExcel

@PostMapping("/excel")
public AjaxResult uploadExcel(@RequestParam("file") MultipartFile file) {
    if (file.isEmpty()) {
        return AjaxResult.error("上传文件不能为空");
    }
    String fileName = file.getOriginalFilename();
    System.out.println("================="+fileName);
    if (fileName == null || (!fileName.endsWith(".xls") &&!fileName.endsWith(".xlsx"))) {
        return AjaxResult.error("请上传后缀名为.xls或.xlsx的文件");
    }
    try {
        Workbook workbook;
        if (fileName.endsWith(".xls")) {
            workbook = new HSSFWorkbook(file.getInputStream());
        } else {
            workbook = new XSSFWorkbook(file.getInputStream());
        }
        Sheet sheet = workbook.getSheetAt(0);  // 获取第一个工作表
        List<MeterReading> meterReadingList = readSheetToMeterReadings(sheet);  // 读取工作表数据转换为 MeterReading 实体列表
        int insertedCount = meterReadingService.insertBatch(meterReadingList);  // 批量插入数据到数据库并获取插入成功的记录数
        workbook.close();
        if (insertedCount > 0) {
            return AjaxResult.success("文件上传成功,共有 " + insertedCount + " 条数据");
        } else {
            return AjaxResult.error("文件上传成功,但数据添加到数据库时出现问题,没有数据成功插入,请检查");
        }
    } catch (IOException e) {
        e.printStackTrace();
        return AjaxResult.error("文件上传失败,请稍后再试");
    }
}
  • 文件处理流程
    • 检查文件是否为空及格式是否正确,不符合则返回错误信息。
    • 根据文件后缀创建对应 Workbook 对象,读取第一个工作表数据。
    • 将工作表数据转换为 MeterReading 实体列表,批量插入数据库并获取插入记录数。
    • 根据插入结果返回相应提示信息,处理过程中出现异常则打印堆栈并返回错误信息。

 (二)工作表数据读取 - readSheetToMeterReadings

private List<MeterReading> readSheetToMeterReadings(Sheet sheet) {
    List<MeterReading> meterReadingList = new ArrayList<>();
    for (Row row : sheet) {
        if (row.getRowNum() == 0) {  // 跳过表头行(假设第一行是表头)
            continue;
        }
        MeterReading meterReading = new MeterReading();
        meterReading.setCode(getCellValue(row.getCell(0), String.class));
        meterReading.setFeeType(getCellValue(row.getCell(1), String.class));
        meterReading.setPreviousReading(getCellValue(row.getCell(2), BigDecimal.class));
        meterReading.setPreviousReadingCount(getCellValue(row.getCell(3), BigDecimal.class));
        meterReading.setPreviousReadingTime(getCellValue(row.getCell(4), Date.class));
        meterReading.setCurrentReading(getCellValue(row.getCell(5), BigDecimal.class));
        meterReading.setCurrentReadingTime(getCellValue(row.getCell(6), Date.class));
        meterReading.setMultiple(getCellValue(row.getCell(7), BigDecimal.class));
        meterReading.setLoss(getCellValue(row.getCell(8), BigDecimal.class));
        meterReadingList.add(meterReading);
    }
    return meterReadingList;
}
  • 数据转换逻辑:遍历工作表行,跳过表头,将每行数据转换为 MeterReading 实体对象并添加到列表,最后返回列表供批量插入使用。

 (三)单元格数据获取 - getCellValue

private <T> T getCellValue(Cell cell, Class<T> targetType) {
    if (cell == null) {
        return null;
    }
    if (targetType == String.class) {
        return (T) cell.getStringCellValue();
    } else if (targetType == BigDecimal.class) {
        return (T) new BigDecimal(cell.getNumericCellValue());
    } else if (targetType == Date.class) {
        return (T) cell.getDateCellValue();
    }
    return null;
}
  • 数据类型转换:根据单元格数据类型和目标类型,将单元格值转换为相应的 Java 数据类型,确保数据准确性和一致性。

此文件完整代码 

package com.ruoyi.web.controller.system;

import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.system.domain.MeterReading;
import com.ruoyi.system.service.IMeterReadingService;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
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.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@RestController
@RequestMapping("/system/upload")
public class ExcelUploadController {

    @Autowired
    private IMeterReadingService meterReadingService;

    @PostMapping("/excel")
    public AjaxResult uploadExcel(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return AjaxResult.error("上传文件不能为空");
        }
        String fileName = file.getOriginalFilename();
        System.out.println("================="+fileName);
        if (fileName == null || (!fileName.endsWith(".xls") &&!fileName.endsWith(".xlsx"))) {
            return AjaxResult.error("请上传后缀名为.xls或.xlsx的文件");
        }
        try {
            Workbook workbook;
            if (fileName.endsWith(".xls")) {
                workbook = new HSSFWorkbook(file.getInputStream());
            } else {
                workbook = new XSSFWorkbook(file.getInputStream());
            }
            Sheet sheet = workbook.getSheetAt(0);  // 获取第一个工作表
            List<MeterReading> meterReadingList = readSheetToMeterReadings(sheet);  // 读取工作表数据转换为 MeterReading 实体列表
            int insertedCount = meterReadingService.insertBatch(meterReadingList);  // 批量插入数据到数据库并获取插入成功的记录数
            workbook.close();
            if (insertedCount > 0) {
                return AjaxResult.success("文件上传成功,共有 " + insertedCount + " 条数据");
            } else {
                return AjaxResult.error("文件上传成功,但数据添加到数据库时出现问题,没有数据成功插入,请检查");
            }
        } catch (IOException e) {
            e.printStackTrace();
            return AjaxResult.error("文件上传失败,请稍后再试");
        }
    }

    private List<MeterReading> readSheetToMeterReadings(Sheet sheet) {
        List<MeterReading> meterReadingList = new ArrayList<>();
        for (Row row : sheet) {
            if (row.getRowNum() == 0) {  // 跳过表头行(假设第一行是表头)
                continue;
            }
            MeterReading meterReading = new MeterReading();
            meterReading.setCode(getCellValue(row.getCell(0), String.class));
            meterReading.setFeeType(getCellValue(row.getCell(1), String.class));
            meterReading.setPreviousReading(getCellValue(row.getCell(2), BigDecimal.class));
            meterReading.setPreviousReadingCount(getCellValue(row.getCell(3), BigDecimal.class));
            meterReading.setPreviousReadingTime(getCellValue(row.getCell(4), Date.class));
            meterReading.setCurrentReading(getCellValue(row.getCell(5), BigDecimal.class));
            meterReading.setCurrentReadingTime(getCellValue(row.getCell(6), Date.class));
            meterReading.setMultiple(getCellValue(row.getCell(7), BigDecimal.class));
            meterReading.setLoss(getCellValue(row.getCell(8), BigDecimal.class));
            meterReadingList.add(meterReading);
        }
        return meterReadingList;
    }

    private <T> T getCellValue(Cell cell, Class<T> targetType) {
        if (cell == null) {
            return null;
        }
        if (targetType == String.class) {
            return (T) cell.getStringCellValue();
        } else if (targetType == BigDecimal.class) {
            return (T) new BigDecimal(cell.getNumericCellValue());
        } else if (targetType == Date.class) {
            return (T) cell.getDateCellValue();
        }
        return null;
    }

    @PostMapping("/checkUploadedData")
    public AjaxResult checkUploadedData() {
        return AjaxResult.success("数据已成功添加到数据库");
    }
}

 实体类

package com.ruoyi.system.domain;

import java.math.BigDecimal;
import java.util.Date;

import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;

/**
 * 抄表数据对象 meter_reading
 * 
 * @author ruoyi
 * @date 2024-12-11
 */
@TableName("meter_reading")
public class MeterReading extends BaseEntity
{
    private static final long serialVersionUID = 1L;

    /** 序号 */
    private Long id;

    /** 编号 */
    @Excel(name = "编号")
    private String code;

    /** 费用类型 */
    @Excel(name = "费用类型")
    private String feeType;

    /** 上期读数 */
    @Excel(name = "上期读数")
    private BigDecimal previousReading;

    /** 上期读表数 */
    @Excel(name = "上期读表数")
    private BigDecimal previousReadingCount;

    /** 上期读表时间 */
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "上期读表时间", width = 30, dateFormat = "yyyy-MM-dd")
    private Date previousReadingTime;

    /** 本期读数 */
    @Excel(name = "本期读数")
    private BigDecimal currentReading;

    /** 本期读表时间 */
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "本期读表时间", width = 30, dateFormat = "yyyy-MM-dd")
    private Date currentReadingTime;

    /** 倍率 */
    @Excel(name = "倍率")
    private BigDecimal multiple;

    /** 损耗 */
    @Excel(name = "损耗")
    private BigDecimal loss;

    public void setId(Long id) 
    {
        this.id = id;
    }

    public Long getId() 
    {
        return id;
    }
    public void setCode(String code) 
    {
        this.code = code;
    }

    public String getCode() 
    {
        return code;
    }
    public void setFeeType(String feeType) 
    {
        this.feeType = feeType;
    }

    public String getFeeType() 
    {
        return feeType;
    }
    public void setPreviousReading(BigDecimal previousReading) 
    {
        this.previousReading = previousReading;
    }

    public BigDecimal getPreviousReading() 
    {
        return previousReading;
    }
    public void setPreviousReadingCount(BigDecimal previousReadingCount) 
    {
        this.previousReadingCount = previousReadingCount;
    }

    public BigDecimal getPreviousReadingCount() 
    {
        return previousReadingCount;
    }
    public void setPreviousReadingTime(Date previousReadingTime) 
    {
        this.previousReadingTime = previousReadingTime;
    }

    public Date getPreviousReadingTime() 
    {
        return previousReadingTime;
    }
    public void setCurrentReading(BigDecimal currentReading) 
    {
        this.currentReading = currentReading;
    }

    public BigDecimal getCurrentReading() 
    {
        return currentReading;
    }
    public void setCurrentReadingTime(Date currentReadingTime) 
    {
        this.currentReadingTime = currentReadingTime;
    }

    public Date getCurrentReadingTime() 
    {
        return currentReadingTime;
    }
    public void setMultiple(BigDecimal multiple) 
    {
        this.multiple = multiple;
    }

    public BigDecimal getMultiple() 
    {
        return multiple;
    }
    public void setLoss(BigDecimal loss) 
    {
        this.loss = loss;
    }

    public BigDecimal getLoss() 
    {
        return loss;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
            .append("id", getId())
            .append("code", getCode())
            .append("feeType", getFeeType())
            .append("previousReading", getPreviousReading())
            .append("previousReadingCount", getPreviousReadingCount())
            .append("previousReadingTime", getPreviousReadingTime())
            .append("currentReading", getCurrentReading())
            .append("currentReadingTime", getCurrentReadingTime())
            .append("multiple", getMultiple())
            .append("loss", getLoss())
            .toString();
    }
}
MeterReadingMapper添加批量插入数据的方法
   int insertBatch(@Param("meterReadings")List<MeterReading> meterReadings);
MeterReadingServiceImpl
/**
     * 批量新增抄表数据
     *
     * @param meterReadings 要批量插入的抄表数据列表
     * @return 实际插入成功的记录数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertBatch(List<MeterReading> meterReadings) {
        if (meterReadings == null || meterReadings.isEmpty()) {
            return 0;
        }
        return meterReadingMapper.insertBatch(meterReadings);
    }

 MeterReadingMapper.xml(这里我是在xml添加这个批量新增sql语句)

<!-- 批量插入抄表数据的SQL映射方法,使用foreach标签遍历传入的meterReadings集合,构建批量插入的VALUES子句 -->
    <insert id="insertBatch" keyProperty="id" useGeneratedKeys="true">
        INSERT INTO meter_reading (code, fee_type, previous_reading, previous_reading_count, previous_reading_time, current_reading, current_reading_time, multiple, loss) VALUES
        <foreach collection="meterReadings" item="item" index="index" separator=",">
            (#{item.code}, #{item.feeType}, #{item.previousReading}, #{item.previousReadingCount}, #{item.previousReadingTime}, #{item.currentReading}, #{item.currentReadingTime}, #{item.multiple}, #{item.loss})
        </foreach>
    </insert>

如果你想和我一样的话,注意放置位置哈 

不要照搬,仅供参考,我懒,bug不想改了!!! 

通过前端与后端代码的紧密配合,实现了完整的 Excel 文件上传功能,包括文件选择、验证、上传、数据解析、入库及结果反馈等环节。此功能在众多数据处理场景中广泛应用,有助于提升数据录入效率和系统实用性。希望本文的解析能助您深入理解其实现原理,为相关开发工作提供参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值