• 微服务项目:尚融宝(17)(后端搭建:数据字典)


    放弃幻想,认清现实,准备斗争

     需求

    一、什么是数据字典

    何为数据字典?数据字典负责管理系统常用的分类数据或者一些固定数据,例如:省市区三级联动数据、民族数据、行业数据、学历数据等,数据字典帮助我们方便的获取和适用这些通用数据。

    二、数据字典的设计

    • parent_id:上级id,通过id与parent_id构建上下级关系,例如:我们要获取所有行业数据,那么只需要查询parent_id=20000的数据
    • name:名称,例如:填写用户信息,我们要select标签选择民族,“汉族”就是数据字典的名称
    • value:值,例如:填写用户信息,我们要select标签选择民族,“1”(汉族的标识)就是数据字典的值
    • dict_code:编码,编码是我们自定义的,全局唯一,例如:我们要获取行业数据,我们可以通过parent_id获取,但是parent_id是不确定的,所以我们可以根据编码来获取行业数据

    这样子的设置避免了多表的雍余

     Excel数据批量导入

    1、添加依赖

    core中添加如下依赖

    1. <dependency>
    2. <groupId>com.alibabagroupId>
    3. <artifactId>easyexcelartifactId>
    4. dependency>
    5. <dependency>
    6. <groupId>org.apache.xmlbeansgroupId>
    7. <artifactId>xmlbeansartifactId>
    8. dependency>

    2、创建Excel实体类 

    1. @Data
    2. public class ExcelDictDTO {
    3. @ExcelProperty("id")
    4. private Long id;
    5. @ExcelProperty("上级id")
    6. private Long parentId;
    7. @ExcelProperty("名称")
    8. private String name;
    9. @ExcelProperty("值")
    10. private Integer value;
    11. @ExcelProperty("编码")
    12. private String dictCode;
    13. }

    3、创建监听器 

    1. @Slf4j
    2. //@AllArgsConstructor //全参
    3. @NoArgsConstructor //无参
    4. public class ExcelDictDTOListener extends AnalysisEventListener {
    5. /**
    6. * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
    7. */
    8. private static final int BATCH_COUNT = 5;
    9. List list = new ArrayList();
    10. private DictMapper dictMapper;
    11. //传入mapper对象
    12. public ExcelDictDTOListener(DictMapper dictMapper) {
    13. this.dictMapper = dictMapper;
    14. }
    15. /**
    16. *遍历每一行的记录
    17. * @param data
    18. * @param context
    19. */
    20. @Override
    21. public void invoke(ExcelDictDTO data, AnalysisContext context) {
    22. log.info("解析到一条记录: {}", data);
    23. list.add(data);
    24. // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
    25. if (list.size() >= BATCH_COUNT) {
    26. saveData();
    27. // 存储完成清理 list
    28. list.clear();
    29. }
    30. }
    31. /**
    32. * 所有数据解析完成了 都会来调用
    33. */
    34. @Override
    35. public void doAfterAllAnalysed(AnalysisContext context) {
    36. // 这里也要保存数据,确保最后遗留的数据也存储到数据库
    37. saveData();
    38. log.info("所有数据解析完成!");
    39. }
    40. /**
    41. * 加上存储数据库
    42. */
    43. private void saveData() {
    44. log.info("{}条数据,开始存储数据库!", list.size());
    45. dictMapper.insertBatch(list); //批量插入
    46. log.info("存储数据库成功!");
    47. }
    48. }

    4、Mapper层批量插入

    接口:DictMapper

    void importData(InputStream inputStream);

    实现:DictServiceImpl 

    1. @Transactional(rollbackFor = {Exception.class})
    2. @Override
    3. public void importData(InputStream inputStream) {
    4. // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
    5. EasyExcel.read(inputStream, ExcelDictDTO.class, new ExcelDictDTOListener(baseMapper)).sheet().doRead();
    6. log.info("importData finished");
    7. }

    注意:此处添加了事务处理,默认情况下rollbackFor = RuntimeException.class 。能保证发现错误或者异常情况出现以后,能及时的回滚。避免数据的不正确

    6、Controller层接收客户端上传

    AdminDictController

    1. @Api(tags = "数据字典管理")
    2. @RestController
    3. @RequestMapping("/admin/core/dict")
    4. @Slf4j
    5. @CrossOrigin
    6. public class AdminDictController {
    7. @Resource
    8. private DictService dictService;
    9. @ApiOperation("Excel批量导入数据字典")
    10. @PostMapping("/import")
    11. public R batchImport(
    12. @ApiParam(value = "Excel文件", required = true)
    13. @RequestParam("file") MultipartFile file) {
    14. try {
    15. InputStream inputStream = file.getInputStream();
    16. dictService.importData(inputStream);
    17. return R.ok().message("批量导入成功");
    18. } catch (Exception e) {
    19. //UPLOAD_ERROR(-103, "文件上传错误"),
    20. throw new BusinessException(ResponseEnum.UPLOAD_ERROR, e);
    21. }
    22. }
    23. }

    7、添加mapper发布配置

    注意:因为maven工程在默认情况下src/main/java目录下的所有资源文件是不发布到target目录下的,因此我们需要在pom.xml中添加xml配置文件发布配置

    1. <build>
    2. <resources>
    3. <resource>
    4. <directory>src/main/javadirectory>
    5. <includes>
    6. <include>**/*.xmlinclude>
    7. includes>
    8. <filtering>falsefiltering>
    9. resource>
    10. resources>
    11. build>

    二、前端调用

    1、创建页面组件

    创建 src/views/core/dict/list.vue

    2、配置路由 

    1. {
    2. path: '/core',
    3. component: Layout,
    4. redirect: '/core/dict/list',
    5. name: 'coreDict',
    6. meta: { title: '系统设置', icon: 'el-icon-setting' },
    7. alwaysShow: true,
    8. children: [
    9. {
    10. path: 'dict/list',
    11. name: '数据字典',
    12. component: () => import('@/views/core/dict/list'),
    13. meta: { title: '数据字典' }
    14. }
    15. ]
    16. },

    3、实现数据导入 

    1. <script>
    2. import dictApi from '@/api/core/dict'
    3. export default {
    4. created() {
    5. console.log("123231231")
    6. this.fetchData()
    7. },
    8. // 定义数据
    9. data() {
    10. return {
    11. dialogVisible: false, //文件上传对话框是否显示
    12. BASE_API: process.env.VUE_APP_BASE_API ,//获取后端接口地址
    13. list: [],//数据字典列表
    14. }
    15. },
    16. methods: {
    17. // 调用api层获取数据库中的数据
    18. fetchData() {
    19. dictApi.listByParentId(1).then(response => {
    20. console.log("8888888888")
    21. this.list = response.data.list
    22. })
    23. },
    24. //延迟加载子节点
    25. getChildren(row, treeNode, resolve) {
    26. dictApi.listByParentId(row.id).then(response => {
    27. //负责将子节点数据展示在展开的列表中
    28. resolve(response.data.list)
    29. })
    30. },
    31. // 上传多于一个文件时
    32. fileUploadExceed() {
    33. this.$message.warning('只能选取一个文件')
    34. },
    35. //上传成功回调
    36. fileUploadSuccess(response) {
    37. if (response.code === 0) {
    38. this.$message.success('数据导入成功')
    39. this.dialogVisible = false
    40. this.fetchData()
    41. } else {
    42. this.$message.error(response.message)
    43. }
    44. },
    45. //上传失败回调
    46. fileUploadError(error) {
    47. this.$message.error('数据导入失败')
    48. },
    49. //Excel数据导出
    50. exportData() {
    51. window.location.href = this.BASE_API + '/admin/core/dict/export'
    52. },
    53. //加载节点
    54. load(tree,treeNode,resolve){
    55. //获取数据
    56. dictApi.listByParentId(tree.id).then(response => {
    57. resolve(response.data.list)
    58. })
    59. }
    60. }
    61. }
    62. script>

    Excel数据批量导出 

    1、Service层解析Excel数据

    接口:DictService

    List listDictData();

    实现:DictServiceImpl 

    1. @Override
    2. public List listDictData() {
    3. List dictList = baseMapper.selectList(null);
    4. //创建ExcelDictDTO列表,将Dict列表转换成ExcelDictDTO列表
    5. ArrayList excelDictDTOList = new ArrayList<>(dictList.size());
    6. dictList.forEach(dict -> {
    7. ExcelDictDTO excelDictDTO = new ExcelDictDTO();
    8. BeanUtils.copyProperties(dict, excelDictDTO);
    9. excelDictDTOList.add(excelDictDTO);
    10. });
    11. return excelDictDTOList;
    12. }

     2、Controller层接收客户端请求

    1. @ApiOperation("Excel数据的导出")
    2. @GetMapping("/export")
    3. public void export(HttpServletResponse response){
    4. try {
    5. // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
    6. response.setContentType("application/vnd.ms-excel");
    7. response.setCharacterEncoding("utf-8");
    8. // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
    9. String fileName = URLEncoder.encode("mydict", "UTF-8").replaceAll("\\+", "%20");
    10. response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
    11. EasyExcel.write(response.getOutputStream(), ExcelDictDTO.class).sheet("数据字典").doWrite(dictService.listDictData());
    12. } catch (IOException e) {
    13. //EXPORT_DATA_ERROR(104, "数据导出失败"),
    14. throw new BusinessException(ResponseEnum.EXPORT_DATA_ERROR, e);
    15. }
    16. }

    二、前端调用

    1、页面添加导出按钮

    1. <el-button
    2. @click="exportData"
    3. type="primary"
    4. size="mini"
    5. icon="el-icon-upload2" >导出Excelel-button>

    2、添加导出方法 

    1. //Excel数据导出
    2. exportData() {
    3. window.location.href = this.BASE_API + '/admin/core/dict/export'
    4. }

    数据字典列表展示 

    一、后端接口

    1、实体类添加属性

    Dict中添加属性

    1. @ApiModelProperty(value = "是否包含子节点")
    2. @TableField(exist = false)//在数据库表中忽略此列
    3. private boolean hasChildren;

    2、Service层实现数据查询

    接口:DictService

    List listByParentId(Long parentId);

    实现:DictServiceImpl 

    1. @Override
    2. public List listByParentId(Long parentId) {
    3. List dictList = baseMapper.selectList(new QueryWrapper().eq("parent_id", parentId));
    4. dictList.forEach(dict -> {
    5. //如果有子节点,则是非叶子节点
    6. boolean hasChildren = this.hasChildren(dict.getId());
    7. dict.setHasChildren(hasChildren);
    8. });
    9. return dictList;
    10. }
    11. /**
    12. * 判断该节点是否有子节点
    13. */
    14. private boolean hasChildren(Long id) {
    15. QueryWrapper queryWrapper = new QueryWrapper().eq("parent_id", id);
    16. Integer count = baseMapper.selectCount(queryWrapper);
    17. if(count.intValue() > 0) {
    18. return true;
    19. }
    20. return false;
    21. }

    3、Controller层接收前端请求 

    1. @ApiOperation("根据上级id获取子节点数据列表")
    2. @GetMapping("/listByParentId/{parentId}")
    3. public R listByParentId(
    4. @ApiParam(value = "上级节点id", required = true)
    5. @PathVariable Long parentId) {
    6. List dictList = dictService.listByParentId(parentId);
    7. return R.ok().data("list", dictList);
    8. }

    二、前端调用

    1、api

    创建 src/api/core/dict.js

    1. import request from '@/utils/request'
    2. export default {
    3. listByParentId(parentId) {
    4. return request({
    5. url: `/admin/core/dict/listByParentId/${parentId}`,
    6. method: 'get'
    7. })
    8. }
    9. }

    2、组件脚本

    定义data

    list: [], //数据字典列表

    生命周期函数 

    1. created() {
    2. this.fetchData()
    3. },

    获取数据的方法 

    import dictApi from '@/api/core/dict'
    1. // 调用api层获取数据库中的数据
    2. fetchData() {
    3. dictApi.listByParentId(1).then(response => {
    4. this.list = response.data.list
    5. })
    6. },
    7. //延迟加载子节点
    8. getChildren(row, treeNode, resolve) {
    9. dictApi.listByParentId(row.id).then(response => {
    10. //负责将子节点数据展示在展开的列表中
    11. resolve(response.data.list)
    12. })
    13. },

    3、组件模板 

    1. "list" border row-key="id" lazy :load="load">
    2. <el-table-column label="名称" align="left" prop="name" />
    3. <el-table-column label="编码" prop="dictCode" />
    4. <el-table-column label="值" align="left" prop="value" />

    4、流程优化

    数据导入后刷新页面的数据列表

    1. //上传成功回调
    2. fileUploadSuccess(response) {
    3. if (response.code === 0) {
    4. this.$message.success('数据导入成功')
    5. this.dialogVisible = false
    6. this.fetchData()
    7. } else {
    8. this.$message.error(response.message)
    9. }
    10. },

    今日异常 

     java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use

    问题有可能是有两个:
    1.没有写启动类:
    2.虽然写了启动类但是启动类所在的包和单元测试的包不在同一级根目录下。
    如:一个是在cn.xxxx.cmcc,另一个是在cn.xxxxx,他们不在同一个目录下所以报 找不到启动类:
    放在同一个包目录下就解决这个问题了。

    总结:单元测试的测试类一定要和启动类在同一个根目录下。
     

  • 相关阅读:
    机器学习笔记之核方法(一)核方法思想与核函数介绍
    手写SpringBoot底层代码(WebServer项目)
    【MultiOTP】在Linux上使用MultiOTP进行SSH登录
    Azure Data Factory(六)数据集类型为Dataverse的Link测试
    Docker下安装RabbitMQ及使用
    使用dnSpy对无源码EXE或DLL进行反编译并且修改
    elementUI的el-menu组件做内部组件和外链区分
    Docker下安装redis
    【C++】构造函数和析构函数
    【Linux】 OpenSSH_9.3p1 升级到 OpenSSH_9.3p2(亲测无问题,建议收藏)
  • 原文地址:https://blog.csdn.net/m0_62436868/article/details/126697488