【学习】若依源码(前后端分离版)之 “ 数据的导入导出功能”

大型纪录片:学习若依源码(前后端分离版)之 “ 数据的导入导出功能”

  • 前言
  • 导入功能实现
    • 前端部分
    • 后端部分
  • 导出功能实现
    • 前端部分
    • 后端部分
    • 8月9日补充:
      • 自定义隐藏属性列
      • 导出对象的子列表
  • 结语

前言

在实际开发中,碰到大批量处理的数据一个一个点击增加真的很麻烦,特别是一些同质化比较大数据。这个时候我们往往会选择用导入功能,通过系统提供的Excel模板批量导入来提高效率。导出自然就不用讲了。

来看若依是怎么实现这些功能的吧,这里我将用我自己定义的一个模块来举例说明。为了方便大家理解,我把若依操作手册(导入导出)也贴出来了。

导入功能实现

前端部分

1、导入按钮

      <el-col :span="1.5">
        <el-button
          type="info"
          plain
          icon="el-icon-upload2"
          size="mini"
          @click="handleImport"
          v-hasPermi="['system:user:import']"
        >导入</el-button>
      </el-col>

2、数据导入的模态框

<!-- 单词导入对话框 -->
    <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
      <el-upload
        ref="upload"
        :limit="1"
        accept=".xlsx, .xls"
        :headers="upload.headers"
        :action="upload.url + '?updateSupport=' + upload.updateSupport"
        :disabled="upload.isUploading"
        :on-progress="handleFileUploadProgress"
        :on-success="handleFileSuccess"
        :auto-upload="false"
        drag
      >
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
        <div class="el-upload__tip text-center" slot="tip">
          <div class="el-upload__tip" slot="tip">
            <el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的用户数据
          </div>
          <span>仅允许导入xls、xlsx格式文件。</span>
          <el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
        </div>
      </el-upload>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitFileForm">确 定</el-button>
        <el-button @click="upload.open = false">取 消</el-button>
      </div>
    </el-dialog>
  </div>
</template>

其中绑定的参数有:

// 单词导入参数
      upload: {
        // 是否显示弹出层(单词导入)
        open: false,
        // 弹出层标题(单词导入)
        title: "",
        // 是否禁用上传
        isUploading: false,
        // 是否更新已经存在的用户数据
        updateSupport: 0,
        // 设置上传的请求头部
        headers: { Authorization: "Bearer " + getToken() },
        // 上传的地址
        url: process.env.VUE_APP_BASE_API + "/Words/words/importData"
      },

如果是自己开发的话,从他原有的系统里拿出来改一些参数也是可以实现这些效果的。当然了,后端也得实现相应的方法。

3、相关的方法

 /** 导入按钮操作 */
    handleImport() {
      this.upload.title = "单词导入";
      this.upload.open = true;
    },
    /** 下载模板操作 */
    importTemplate() {
      this.download('Words/words/importTemplate', {
      }, `小糖的单词导入模板_(。・∀・)ノ゙嗨${new Date().getTime()}.xlsx`)
    },
    // 文件上传中处理
    handleFileUploadProgress(event, file, fileList) {
      this.upload.isUploading = true;
    },
    // 文件上传成功处理
    handleFileSuccess(response, file, fileList) {
      this.upload.open = false;
      this.upload.isUploading = false;
      this.$refs.upload.clearFiles();
      this.$alert(response.msg, "导入结果", { dangerouslyUseHTMLString: true });
      this.getList();
    },
    // 提交上传文件
    submitFileForm() {
      this.$refs.upload.submit();
    }

4、效果展示
【学习】若依源码(前后端分离版)之 “ 数据的导入导出功能”_第1张图片

后端部分

1、我们来看“下载模板”的功能是怎么实现的,根据先看相关代码

**在实体变量上添加@Excel注解**

/** 单词ID */
    @Excel(name = "单词ID(更新数据需要)")
    private Long wId;

    /** 音标 */
    @Excel(name = "音标")
    private String wSymbol;

    /** 单词 */
    @Excel(name = "单词")
    private String wName;

    /** 单词难度(分五级) */
    @Excel(name = "单词难度(1-5)", readConverterExp = "1=一级,2=二级,3=三级,4=四级,5=五级")
    private Integer wDifficultly;

    /** 备注 */
    @Excel(name = "备注")
    private String wNote;
    @PostMapping("/importTemplate")
    public void importTemplate(HttpServletResponse response)
    {
        ExcelUtil<Words> util = new ExcelUtil<Words>(Words.class);
        util.importTemplateExcel(response, "用户数据");
    }

    /**
     * 对list数据源将其里面的数据导入到excel表单
     * 
     * @param sheetName 工作表的名称
     * @param title 标题
     * @return 结果
     */
    public void importTemplateExcel(HttpServletResponse response, String sheetName, String title)
    {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        this.init(null, sheetName, title, Type.IMPORT);
        exportExcel(response);
    }

这个方法的意思是,它用于下载单词数据的导入模板,也就是一个空白的Excel文件,里面只有表头,方便用户填写数据并上传。具体的步骤如下:

  • 首先,它创建了一个ExcelUtil对象,这是若依封装的一个工具类,用于操作Excel文件。它传入了Words.class作为参数,表示要导入的实体类类型是系统用户。
  • 然后,它调用了ExcelUtil对象的importTemplateExcel方法,这个方法会根据实体类的@Excel注解生成表头,并将Excel文件写入到HttpServletResponse对象中。它传入了response作为参数,表示要写入的响应对象,和"用户数据"作为参数,表示要生成的Excel文件的名称。
  • 最后,当用户访问这个接口时,他们就可以下载一个名为"用户数据.xlsx"的导入模板文件,里面只有一行表头,例如"用户ID",“用户名”,"密码"等。用户可以在这个文件中填写自己的数据,并通过另一个接口上传到系统中。

接下来我举一个简单的例子来解释一下:

假设你有一个网站,你想让用户可以下载一些文件,比如图片,音乐,文档等。你怎么做呢?

你可能会在网站上放一个链接,比如"点击这里下载图片",当用户点击这个链接时,他们的浏览器就会打开一个对话框,让他们选择保存文件的位置和名称。这个过程就是将文件写入到HttpServletResponse对象中。

HttpServletResponse对象是一个Java类,它表示服务器给客户端(也就是用户的浏览器)发送的响应。它包含了响应的内容,类型,状态码等信息。

当你想让用户下载一个文件时,你需要设置响应的类型为application/vnd.ms-excel,这表示响应的内容是一个Excel文件。你还需要设置响应的头部,比如Content-Disposition,这表示响应的内容是一个附件,而不是直接显示在浏览器中。你还可以指定附件的名称,比如"用户数据.xlsx"。

然后,你需要使用EasyExcel这个工具类来生成一个Excel文件,并将它写入到HttpServletResponse对象的输出流中。输出流是一个通道,它可以将数据从服务器发送到客户端。当你把Excel文件写入到输出流中时,客户端就会收到这个文件,并弹出对话框让用户保存。

所以,这一段话的意思就是:若依使用了EasyExcel这个工具类来生成一个Excel文件,并将它写入到HttpServletResponse对象中。它传入了response作为参数,表示要写入的响应对象,和"用户数据"作为参数,表示要生成的Excel文件的名称。

2、现在我们可以来看“导入数据”的功能是怎么实现的:

    @Log(title = "单词管理", businessType = BusinessType.IMPORT)
    @PreAuthorize("@ss.hasPermi('system:user:import')")
    @PostMapping("/importData")
    public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
    {
        ExcelUtil<Words> util = new ExcelUtil<Words>(Words.class);
        List<Words> wordsList = util.importExcel(file.getInputStream());
        String operName = getUsername();
        System.out.println(wordsList);
        String message = wordsService.importWord(wordsList, updateSupport, operName);
        return AjaxResult.success(message);
    }

重点看一下实现类wordsService.importWord方法

/**
     * 导入单词数据
     *
     * @param wordsList 单词数据列表
     * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据
     * @param operName 操作用户
     * @return 结果
     */
    @Override
    public String importWord(List<Words> wordsList, Boolean isUpdateSupport, String operName)
    {
        if (StringUtils.isNull(wordsList) || wordsList.size() == 0)
        {
            throw new ServiceException("导入单词数据不能为空!");
        }
        int successNum = 0;
        int failureNum = 0;
        StringBuilder successMsg = new StringBuilder();
        StringBuilder failureMsg = new StringBuilder();
        for (Words words : wordsList)
        {
            System.out.println(words);
            try
            {
                // 验证是否存在这个单词
                Words u = wordsMapper.selectWordsByWName(words.getwName());
                if (StringUtils.isNull(u))
                {
                    successNum++;
                    this.insertWords(words);
                    successMsg.append("
"
+ successNum + "、单词 " + words.getwName() + " 导入成功"); } else if (isUpdateSupport) { words.setUpdateBy(operName); this.updateWords(words); successNum++; successMsg.append("
"
+ successNum + "、单词 " + words.getwName() + " 更新成功"); } else { failureNum++; failureMsg.append("
"
+ failureNum + "、单词 " + words.getwName() + " 已存在"); } } catch (Exception e) { failureNum++; String msg = "
"
+ failureNum + "、单词 " + words.getwName() + " 导入失败:"; failureMsg.append(msg + e.getMessage()); log.error(msg, e); } } if (failureNum > 0) { failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); throw new ServiceException(failureMsg.toString()); } else { successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); } return successMsg.toString(); }

这部分代码定义了一个名为importUser的方法,用于导入用户数据。具体的步骤如下:

  • 首先,它接收了三个参数:
    • wordsList: 这是一个Words类型的列表,表示要导入的用户数
    • isUpdateSupport: 这是一个Boolean类型的值,表示是否支持更新已存在的用户数据。
    • operName: 这是一个String类型的值,表示操作用户的名称。
  • 然后,它判断了userList是否为空或者长度为0,如果是,则抛出一个ServiceException异常,提示"导入用户数据不能为空!"。
  • 接着,它定义了四个变量原来表示导入成功、失败的数量,和成功、失败的信息。
  • 接着,它遍历了wordsList中的每个words对象,执行以下操作:
    • 首先,它使用wordsMapper中的selectWordsByWName方法,根据Words对象的wName属性查询数据库中是否存在,并将结果赋给了u变量。这是一个Words类型的对象,如果不存在则为null。
    • 然后,它判断了u是否为null,如果是,则表示这个单词不存在,则去创建这个单词。
    • 否则,如果u不为null,则表示这个用户已存在,则判断isUpdateSupport是否为true,如果是,则表示支持更新已存在的数据;如果否,则表示不支持更新已存在的用户数据。
  • 最后,它判断了failureNum是否大于0,如果是,则表示有导入失败的数据,这会终止方法的执行,并返回错误信息给调用者;如果failureNum不大于0,则表示没有导入失败的用户数据,则正常结束方法的执行,并返回成功信息给调用者。

以上就是导入功能的整套流程了,接下来来看导出部分。


导出功能实现

前端部分

1、导出按钮

<el-col :span="1.5">
        <el-button
          type="warning"
          plain
          icon="el-icon-download"
          size="mini"
          @click="handleExport"
          v-hasPermi="['Words:words:export']"
        >导出</el-button>
      </el-col>

2、导出请求方法(参考如下)

// 导出接口exportUser
import { exportUser } from "@/api/system/user";

/** 导出按钮操作 */
    handleExport() {
      this.download('Words/words/export', {
        ...this.queryParams
      }, `导出单词_q(≧▽≦q)${new Date().getTime()}.xlsx`)
    },

后端部分

1、导出按钮的请求就对应该方法

    /**
     * 导出单词实体类列表
     */
    @PreAuthorize("@ss.hasPermi('Words:words:export')")
    @Log(title = "单词实体类", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, Words words)
    {
        List<Words> list = wordsService.selectWordsList(words);
        ExcelUtil<Words> util = new ExcelUtil<Words>(Words.class);
        util.exportExcel(response, list, "单词实体类数据");
    }

2、service层实现方法

    /**
     * 根据条件分页查询未分配用户角色列表
     *
     * @param user 用户信息
     * @return 用户信息集合信息
     */
        /**
     * 查询单词实体类列表
     * 
     * @param words 单词实体类
     * @return 单词实体类
     */
    @Override
    public List<Words> selectWordsList(Words words)
    {
        return wordsMapper.selectWordsList(words);
    }


稍微解释一下吧:

  • 首先,它使用了@PreAuthorize这个注解,表示这个方法需要有"Words:words:export"这个权限才能执行。这是一个Spring Security的功能,用于控制访问权限。
  • 然后,它使用了@Log这个注解,表示这个方法的执行会被记录到日志中。
  • 接着,它调用了wordsService中的selectWordsList方法,传入了words对象作为参数,查询数据库中符合条件的单词实体类列表,并将结果赋给了list变量。这是一个Words类型的列表,表示要导出的数据。
  • 然后,它创建了一个ExcelUtil对象,这是若依封装的一个工具类,用于操作Excel文件。它传入了Words.class作为参数,表示要导出的实体类类型是单词实体类。
  • 最后,它调用了ExcelUtil对象的exportExcel方法,传入了三个参数:
    • response: 这是一个HttpServletResponse类型的对象,表示要写入的响应对象。
    • list: 这是一个Words类型的列表,表示要导出的数据。
    • “单词实体类数据”: 这是一个字符串,表示要生成的Excel文件的名称。

这样,当用户调用这个方法时,它就会根据查询条件从数据库中获取单词实体类列表,并将其导出为一个Excel文件,并写入到响应对象中。用户就可以下载这个文件,并查看其中的数据。


8月9日补充:

自定义隐藏属性列

有时候我们希望对列信息根据业务去动态显示,那么我们可以进行如下处理。

示例:对用户进行条件判断,符合条件则隐藏属性。导出的文件则不会显示此列信息。

@PostMapping("/export")
public void export(HttpServletResponse response, SysUser user)
{
	List<SysUser> list = userService.selectUserList(user);
	ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
	if (条件A) {
	  // 不显示用户ID(单个)
	  util.hideColumn("userId");
	} else if (条件B) {
	  // 不显示用户名称、用户手机(多个)
	  util.hideColumn("userId", "phonenumber");
	} } else if (条件C) {
	  // 不显示用户邮箱、部门名称(子对象)
	  util.hideColumn("email", "dept.deptName");
	}
	util.exportExcel(response, list, "用户数据");
}

导出对象的子列表

有时候对象里面还包含集合列表,例如用户管理包含多个角色需要导出,那么我们可以进行如下处理。

SysUser.java

public class SysUser
{
    @Excel(name = "用户编号", cellType = ColumnType.NUMERIC, width = 20, needMerge = true)
    private String userId;

    @Excel(name = "用户名称", width = 20, needMerge = true)
    private String userName;

    @Excel(name = "邮箱", width = 20, needMerge = true)
    private String email;

    @Excel(name = "角色")
    private List<SysRole> roles;

    public String getUserId()
    {
        return userId;
    }

    public void setUserId(String userId)
    {
        this.userId = userId;
    }

    public String getUserName()
    {
        return userName;
    }

    public void setUserName(String userName)
    {
        this.userName = userName;
    }

    public String getEmail()
    {
        return email;
    }

    public void setEmail(String email)
    {
        this.email = email;
    }

    public List<SysRole> getRoles()
    {
        return roles;
    }

    public void setRoles(List<SysRole> roles)
    {
        this.roles = roles;
    }
}

SysRole.java

public class SysRole
{
    @Excel(name = "角色编号", cellType = ColumnType.NUMERIC)
    private String roleId;

    @Excel(name = "角色名称")
    private String roleName;

    @Excel(name = "角色字符")
    private String roleKey;

    public String getRoleId()
    {
        return roleId;
    }

    public void setRoleId(String roleId)
    {
        this.roleId = roleId;
    }

    public String getRoleName()
    {
        return roleName;
    }

    public void setRoleName(String roleName)
    {
        this.roleName = roleName;
    }

    public String getRoleKey()
    {
        return roleKey;
    }

    public void setRoleKey(String roleKey)
    {
        this.roleKey = roleKey;
    }

}

效果为:
【学习】若依源码(前后端分离版)之 “ 数据的导入导出功能”_第2张图片

结语

可能牵扯到更深的代码讲的不是很清楚,如有疑问多多评论留言,一起探讨交流吧。

那么以上就是唐某的一些理解。这次的分享就到这里了。记得一键三连~( •̀ ω •́ )✧

你可能感兴趣的:(若依学习,学习,java,spring,spring,boot,ruoyi)