项目5:实现数据字典的上传下载

项目5:实现数据字典的上传下载

1.什么是数据字典?如何设计?

2.业务流程逻辑

3.数据库表的设计

4.实现上传下载逻辑(前端)

5.实现上传逻辑(后端)

6.实现下载依赖(后端)

项目5:实现数据字典的上传下载

1.什么是数据字典?如何设计?

①什么是数据字典?

数据字典是用来管理系统常用的分类数据和固定设计

②数据字典的设计?

  • id
  • parent_id
  • 即parent_id即为本标题的父标题,没有则为0

③数据字典中数据库对应后端pojo

  • 数据库中的数据字典(Dict对象)
    需要存储creat_time,update_time,is_deleted
  • 后端pojo
    对应具体的excel即传输对象,不需要上述三个字段的存储,故对象为(ExcelDictDTO)

2.业务流程逻辑

①上传excel表

  • 前端单击上传
  • 后端接收到excel表解析为ExcelDictDTO对象
  • 然后通过mapper保存到数据库中

②下载excel表

  • 前端单击下载
  • 后端接受到请求后通过mapper查询数据库并且封装为ExcelDictDTO对象
  • 然后通过http响应返回文件

③注意

  • ajax请求只能解析字符串
  • http请求可以解析字符串,解析文件
  • 故在上传时可以用ajax请求
  • 故在下载时只能用普通的请求

3.数据库表的设计

①数据库表

在这里插入图片描述

②数据库表对应的entity

package com.atguigu.srb.core.pojo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableField;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * <p>
 * 数据字典
 * </p>
 *
 * @author Likejin
 * @since 2023-04-09
 */
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="Dict对象", description="数据字典")
public class Dict implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "id")
      @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @ApiModelProperty(value = "上级id")
    private Long parentId;

    @ApiModelProperty(value = "名称")
    private String name;

    @ApiModelProperty(value = "值")
    private Integer value;

    @ApiModelProperty(value = "编码")
    private String dictCode;

    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;

    @ApiModelProperty(value = "更新时间")
    private LocalDateTime updateTime;

    @ApiModelProperty(value = "删除标记(0:不可用 1:可用)")
    @TableField("is_deleted")
    @TableLogic
    private Boolean deleted;


}

③数据传输对象dto(与excel对应)

package com.atguigu.srb.core.pojo.dto;

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

@Data
public class ExcelDictDTO {

    @ExcelProperty("id")
    private Long id;

    @ExcelProperty("上级id")
    private Long parentId;

    @ExcelProperty("名称")
    private String name;

    @ExcelProperty("值")
    private Integer value;

    @ExcelProperty("编码")
    private String dictCode;
}

4.实现上传逻辑(前端)

①在路由中加入系统设置/数据字典路由

在这里插入图片描述
srb-admin\src\router\index.js

{
    path: '/core',
    component: Layout,
    redirect: '/core/dict/list',
    name: 'coreDict',
    meta: { title: '系统设置', icon: 'el-icon-setting' },
    alwaysShow: true,
    children: [
      {
        path: 'dict/list',
        name: '数据字典',
        component: () => import('@/views/core/dict/list'),
        meta: { title: '数据字典' },
      },
    ],
  },

②新建页面组件(配合路由渲染主内容区)

srb-admin\src\views\core\dict\list.vue

<template>
  <div class="app-container">
    <!--Excel导入按钮-->
    <div style="margin-bottom: 10px">
      <el-button
        @click="dialogVisible = true"
        type="primary"
        size="mini"
        icon="el-icon-download"
      >
        导入Excel
      </el-button>
      <el-button
        @click="exportData"
        type="primary"
        size="mini"
        icon="el-icon-upload2"
      >
        导出Excel
      </el-button>
    </div>

    <!--显示一个dialog对话框-->
    <el-dialog title="数据字典导入" :visible.sync="dialogVisible" width="30%">
      <el-form>
        <el-form-item label="请选择Excel文件">
          <!--accept为只接受xls,xlsx的文件后缀,
          name名字必须要和后端的名字保持一致,
          avtion为提交地址发起ajax远程调用(和自己写的request请求无关),自己处理成功失败-->
          <el-upload
            :auto-upload="true"
            :multiple="false"
            :limit="1"
            :on-exceed="fileUploadExceed"
            :on-success="fileUploadSuccess"
            :on-error="fileUploadError"
            :action="BASE_API + '/admin/core/dict/import'"
            name="file"
            accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
          >
            <el-button size="small" type="primary">点击上传</el-button>
          </el-upload>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取消</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
export default {
  data() {
    return {
      dialogVisible: false, //对话框是否显式
      BASE_API: process.env.VUE_APP_BASE_API, //获取后端接口地址
    }
  },
  methods: {
    // 上传多于一个文件时
    fileUploadExceed() {
      this.$message.warning('只能选取一个文件')
    },

    //上传成功回调(http通信成功,有可能业务失败或业务失败)
    fileUploadSuccess(response) {
      if (response.code === 0) {
        //业务成功
        this.$message.success('数据导入成功')
        this.dialogVisible = false
      } else {
        //业务事变
        this.$message.error(response.message)
      }
    },

    //上传失败回调(http通信不成功)
    fileUploadError(error) {
      this.$message.error('数据导入失败')
    },

    //Excel数据导出
    exportData() {
      //导出excel,此时不能用ajax请求,无法响应文件
      window.location.href = this.BASE_API + '/admin/core/dict/export'
    },
  },
}
</script>

5.实现上传逻辑(后端)

①逻辑流程

  • 前端单击上传(发起ajax请求)
  • 后端controller接收到文件类型数据,调用service层保存
  • service层调用easyexcel接口读取excel数据(利用监听器)
  • 监听器调用mapper接口存储excel数据到数据库

②创建dto数据传输对象(封装excel表格数据)

package com.atguigu.srb.core.pojo.dto;

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

@Data
public class ExcelDictDTO {

    @ExcelProperty("id")
    private Long id;

    @ExcelProperty("上级id")
    private Long parentId;

    @ExcelProperty("名称")
    private String name;

    @ExcelProperty("值")
    private Integer value;

    @ExcelProperty("编码")
    private String dictCode;
}

③引入easyexcel依赖

        <!--excel依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
        </dependency>
        <!--excel底层用xml存储,需要xml解析器-->
        <dependency>
            <groupId>org.apache.xmlbeans</groupId>
            <artifactId>xmlbeans</artifactId>
        </dependency>

④编写controller接口(接受文件调用sevice存储数据库)

package com.atguigu.srb.core.controller.admin;


import com.alibaba.excel.EasyExcel;
import com.atguigu.common.exception.BusinessException;
import com.atguigu.common.result.R;
import com.atguigu.common.result.ResponseEnum;
import com.atguigu.srb.core.pojo.dto.ExcelDictDTO;
import com.atguigu.srb.core.service.DictService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;

/**
 * <p>
 * 数据字典 前端控制器
 * </p>
 *
 * @author Likejin
 * @since 2023-04-09
 */
@Api(tags = "数据字典管理")
@RestController
@RequestMapping("/admin/core/dict")
@Slf4j
@CrossOrigin
public class AdminDictController {

    @Resource
    DictService dictService;


    @ApiOperation("Excel数据的批量导入(导入到数据库)")
    @PostMapping("/import")
    public R batchImport(
            @ApiParam(value="Excel数据字典文件")
            @RequestParam("file") MultipartFile file){

        try {
            InputStream inputStream = file.getInputStream();
            dictService.importData(inputStream);
            return R.ok().message("数据字典批量导入成功");
        } catch (Exception e) {
            //返回R对象,并且打印异常跟踪栈
            throw new BusinessException(ResponseEnum.UPLOAD_ERROR,e);
        }

    }

    @ApiOperation("Excel数据的导出")
    //访问页面
    @GetMapping("/export")
    public void export(HttpServletResponse response){
        try {
            // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
            //设置类型为excel
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            String fileName = URLEncoder.encode("mydict", "UTF-8").replaceAll("\\+", "%20");
            //设置附件的形式下载到浏览器端
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
            EasyExcel.write(response.getOutputStream(), ExcelDictDTO.class).sheet("数据字典").doWrite(dictService.listDictData());

        } catch (IOException e) {
            //EXPORT_DATA_ERROR(104, "数据导出失败"),
            throw  new BusinessException(ResponseEnum.EXPORT_DATA_ERROR, e);
        }
    }

}


⑤编写service接口和serviceimpl

package com.atguigu.srb.core.service;

import com.atguigu.srb.core.pojo.dto.ExcelDictDTO;
import com.atguigu.srb.core.pojo.entity.Dict;
import com.baomidou.mybatisplus.extension.service.IService;

import java.io.InputStream;
import java.util.List;

/**
 * <p>
 * 数据字典 服务类
 * </p>
 *
 * @author Likejin
 * @since 2023-04-09
 */
public interface DictService extends IService<Dict> {


    void importData(InputStream inputStream);

    List<ExcelDictDTO> listDictData();
}

package com.atguigu.srb.core.service.impl;

import com.alibaba.excel.EasyExcel;
import com.atguigu.srb.core.listener.ExcelDictDTOListener;
import com.atguigu.srb.core.mapper.DictMapper;
import com.atguigu.srb.core.pojo.dto.ExcelDictDTO;
import com.atguigu.srb.core.pojo.entity.Dict;
import com.atguigu.srb.core.service.DictService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 数据字典 服务实现类
 * </p>
 *
 * @author Likejin
 * @since 2023-04-09
 */
@Slf4j
@Service
public class DictServiceImpl extends ServiceImpl<DictMapper, Dict> implements DictService {

    @Resource
    private  DictMapper dictMapper;

    //读取数据
    //写入数据库,利用事务的操作成功全成功,失败全失败(只要异常出现则回滚)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void importData(InputStream inputStream) {
        //传入输入流,
        EasyExcel.read(inputStream, ExcelDictDTO.class, new ExcelDictDTOListener(dictMapper)).sheet().doRead();
        log.info("Excel导入成功");
    }

    @Override
    public List<ExcelDictDTO> listDictData() {
        List<Dict> dictList = dictMapper.selectList(null);
        //创建ExcelDictDTO列表,将Dict列表转化为ExcelDictDTO列表
        ArrayList<ExcelDictDTO> excelDictDTOList = new ArrayList<>(dictList.size());
        dictList.forEach(dict -> {

            ExcelDictDTO excelDictDTO = new ExcelDictDTO();
            //对象拷贝,dict拷贝到excelDictDTO(相同列拷贝)
            BeanUtils.copyProperties(dict, excelDictDTO);
            excelDictDTOList.add(excelDictDTO);
        });
        return excelDictDTOList;
    }
}

⑥编写excel读取监听器并调用mapper接口

package com.atguigu.srb.core.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.atguigu.srb.core.mapper.DictMapper;
import com.atguigu.srb.core.pojo.dto.ExcelDictDTO;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@NoArgsConstructor
public class ExcelDictDTOListener extends AnalysisEventListener<ExcelDictDTO> {

    //@Resource注入mapper不行,因为本类并没有被spring管理,需要用参数构造的方式加入mapper
    private DictMapper dictMapper;

    public ExcelDictDTOListener(DictMapper dictMapper){
        this.dictMapper = dictMapper;
    }

    //数据列表
    List<ExcelDictDTO> list = new ArrayList<ExcelDictDTO>();

    //每隔五条记录存储一次数据
    static final  int BATCH_SIZE=5;
    @Override
    public void invoke(ExcelDictDTO data, AnalysisContext analysisContext) {
        log.info("解析到一条数据{}",data);
        //调用mapper层的save数据
        //批量解析,列表达到一定数量,然后一次性存储数据

        //数据存入数据列表
        list.add(data);
        if(list.size()>=BATCH_SIZE){
            saveData();
            list.clear();
        }
    }

    private void saveData() {
        log.info("{}条数据被存储到数据库",list.size());
        //调用mapper层的批量save方法
        //TODO
        dictMapper.insertBatch(list);
        log.info("{}条数据被存储到数据库成功",list.size());
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        //最后剩余的数据不足BATCH_SIZE,最终一次性存储剩余数据
        saveData();
        log.info("解析完成");
    }
}

⑦编写mapper接口和具体的sql语句

package com.atguigu.srb.core.mapper;

import com.atguigu.srb.core.pojo.dto.ExcelDictDTO;
import com.atguigu.srb.core.pojo.entity.Dict;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import java.util.List;

/**
 * <p>
 * 数据字典 Mapper 接口
 * </p>
 *
 * @author Likejin
 * @since 2023-04-09
 */
public interface DictMapper extends BaseMapper<Dict> {

    void insertBatch(List<ExcelDictDTO> list);
}

<?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.atguigu.srb.core.mapper.DictMapper">

    <insert id="insertBatch">
        insert into dict (
        id ,
        parent_id ,
        name ,
        value ,
        dict_code
        ) values
        <foreach collection="list" item="item" index="index" separator=",">
            (
            #{item.id} ,
            #{item.parentId} ,
            #{item.name} ,
            #{item.value} ,
            #{item.dictCode}
            )
        </foreach>
    </insert>

</mapper>

⑧配置打包时打包JAVA文件内的xml

  • JAVA目录下的xml不会被打包,只会打包.java
  • 代码生成器生成的xml文件都放在java包下
  • 两种解决方法:
    将xml文件都放入resources下
    打包方式改变
  • 使用打包方式
    <build>
        <!-- 项目打包时会将java目录中的*.xml文件也进行打包 -->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

6.实现下载依赖(后端)

①业务流程

  • 前端单击下载(向后端发起非ajax请求,可以不用设置跨域)
  • 后端controller接受请求,controller调用easyexcel接口返回excel文件
  • easyexcel接口利用service的获取到的数据封装为excel
  • 注意:
    调用代码生成器的service接口获得的是Dict类型数据,需要封装为ExcelDictDTO数据返回excel文件

②controller

package com.atguigu.srb.core.controller.admin;


import com.alibaba.excel.EasyExcel;
import com.atguigu.common.exception.BusinessException;
import com.atguigu.common.result.R;
import com.atguigu.common.result.ResponseEnum;
import com.atguigu.srb.core.pojo.dto.ExcelDictDTO;
import com.atguigu.srb.core.service.DictService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;

/**
 * <p>
 * 数据字典 前端控制器
 * </p>
 *
 * @author Likejin
 * @since 2023-04-09
 */
@Api(tags = "数据字典管理")
@RestController
@RequestMapping("/admin/core/dict")
@Slf4j
@CrossOrigin
public class AdminDictController {

    @Resource
    DictService dictService;


    @ApiOperation("Excel数据的批量导入(导入到数据库)")
    @PostMapping("/import")
    public R batchImport(
            @ApiParam(value="Excel数据字典文件")
            @RequestParam("file") MultipartFile file){

        try {
            InputStream inputStream = file.getInputStream();
            dictService.importData(inputStream);
            return R.ok().message("数据字典批量导入成功");
        } catch (Exception e) {
            //返回R对象,并且打印异常跟踪栈
            throw new BusinessException(ResponseEnum.UPLOAD_ERROR,e);
        }

    }

    @ApiOperation("Excel数据的导出")
    //访问页面
    @GetMapping("/export")
    public void export(HttpServletResponse response){
        try {
            // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
            //设置类型为excel
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            String fileName = URLEncoder.encode("mydict", "UTF-8").replaceAll("\\+", "%20");
            //设置附件的形式下载到浏览器端
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
            EasyExcel.write(response.getOutputStream(), ExcelDictDTO.class).sheet("数据字典").doWrite(dictService.listDictData());

        } catch (IOException e) {
            //EXPORT_DATA_ERROR(104, "数据导出失败"),
            throw  new BusinessException(ResponseEnum.EXPORT_DATA_ERROR, e);
        }
    }

}


③service层

  • 封装代码生成器返回Dict的list为DTO的list
package com.atguigu.srb.core.service;

import com.atguigu.srb.core.pojo.dto.ExcelDictDTO;
import com.atguigu.srb.core.pojo.entity.Dict;
import com.baomidou.mybatisplus.extension.service.IService;

import java.io.InputStream;
import java.util.List;

/**
 * <p>
 * 数据字典 服务类
 * </p>
 *
 * @author Likejin
 * @since 2023-04-09
 */
public interface DictService extends IService<Dict> {


    void importData(InputStream inputStream);

    List<ExcelDictDTO> listDictData();
}

package com.atguigu.srb.core.service.impl;

import com.alibaba.excel.EasyExcel;
import com.atguigu.srb.core.listener.ExcelDictDTOListener;
import com.atguigu.srb.core.mapper.DictMapper;
import com.atguigu.srb.core.pojo.dto.ExcelDictDTO;
import com.atguigu.srb.core.pojo.entity.Dict;
import com.atguigu.srb.core.service.DictService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 数据字典 服务实现类
 * </p>
 *
 * @author Likejin
 * @since 2023-04-09
 */
@Slf4j
@Service
public class DictServiceImpl extends ServiceImpl<DictMapper, Dict> implements DictService {

    @Resource
    private  DictMapper dictMapper;

    //读取数据
    //写入数据库,利用事务的操作成功全成功,失败全失败(只要异常出现则回滚)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void importData(InputStream inputStream) {
        //传入输入流,
        EasyExcel.read(inputStream, ExcelDictDTO.class, new ExcelDictDTOListener(dictMapper)).sheet().doRead();
        log.info("Excel导入成功");
    }

    @Override
    public List<ExcelDictDTO> listDictData() {
        List<Dict> dictList = dictMapper.selectList(null);
        //创建ExcelDictDTO列表,将Dict列表转化为ExcelDictDTO列表
        ArrayList<ExcelDictDTO> excelDictDTOList = new ArrayList<>(dictList.size());
        dictList.forEach(dict -> {

            ExcelDictDTO excelDictDTO = new ExcelDictDTO();
            //对象拷贝,dict拷贝到excelDictDTO(相同列拷贝)
            BeanUtils.copyProperties(dict, excelDictDTO);
            excelDictDTOList.add(excelDictDTO);
        });
        return excelDictDTOList;
    }
}

未更新

未更新

未更新

未更新

未更新

未更新

未更新

未更新

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值