Microservice project: Shang Rongbao (17) (back-end construction: data dictionary)

Abandon fantasy, recognize reality, prepare to fight

 need

1. What is a data dictionary

What is a data dictionary? The data dictionary is responsible for managing the classified data or some fixed data commonly used in the system, such as: provincial and urban three-level linkage data, ethnic data, industry data, education data, etc. The data dictionary helps us to easily obtain and apply these general data.

Second, the design of the data dictionary

  • parent_id: parent id, build the relationship between parent and parent through id and parent_id, for example: if we want to get all industry data, then we only need to query the data of parent_id=20000
  • name: name, for example: fill in user information, we need to select the label to select the nationality, "Han nationality" is the name of the data dictionary
  • value: value, for example: fill in the user information, we want to select the label to select the nationality, "1" (the identification of the Han nationality) is the value of the data dictionary
  • dict_code: encoding, the encoding is customized and globally unique, for example: if we want to obtain industry data, we can obtain it through parent_id, but parent_id is uncertain, so we can obtain industry data according to the code

This setting avoids the long excess of multiple tables

 Excel data batch import

1. Add dependencies

Add the following dependencies to core

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
</dependency>

<dependency>
    <groupId>org.apache.xmlbeans</groupId>
    <artifactId>xmlbeans</artifactId>
</dependency>

2. Create an Excel entity class 

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

3. Create a listener 

@Slf4j
//@AllArgsConstructor //全参
@NoArgsConstructor //无参
public class ExcelDictDTOListener extends AnalysisEventListener<ExcelDictDTO> {

    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    List<ExcelDictDTO> list = new ArrayList();

    private DictMapper dictMapper;

	//传入mapper对象
    public ExcelDictDTOListener(DictMapper dictMapper) {
        this.dictMapper = dictMapper;
    }

    /**
     *遍历每一行的记录
     * @param data
     * @param context
     */
    @Override
    public void invoke(ExcelDictDTO data, AnalysisContext context) {
        log.info("解析到一条记录: {}", data);
        list.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            list.clear();
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", list.size());
        dictMapper.insertBatch(list);  //批量插入
        log.info("存储数据库成功!");
    }
}

4. Batch insert of Mapper layer

Interface: DictMapper

void importData(InputStream inputStream);

Implementation: DictServiceImpl 

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

Note: Transaction handling is added here, rollbackFor = RuntimeException.class by default. It can ensure that after an error or abnormal situation occurs, it can be rolled back in time. Avoid incorrect data

6. The Controller layer receives client uploads

AdminDictController

@Api(tags = "数据字典管理")
@RestController
@RequestMapping("/admin/core/dict")
@Slf4j
@CrossOrigin
public class AdminDictController {

    @Resource
    private DictService dictService;

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

        try {
            InputStream inputStream = file.getInputStream();
            dictService.importData(inputStream);
            return R.ok().message("批量导入成功");
        } catch (Exception e) {
            //UPLOAD_ERROR(-103, "文件上传错误"),
            throw new BusinessException(ResponseEnum.UPLOAD_ERROR, e);
        }
    }
}

7. Add mapper release configuration

Note: Because all resource files in the src/main/java directory of the maven project are not published to the target directory by default, we need to add the xml configuration file release configuration in pom.xml

<build>
    <!-- 项目打包时会将java目录中的*.xml文件也进行打包 -->
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
</build>

Second, the front-end call

1. Create page components

Create src/views/core/dict/list.vue

<template>
  <div class="app-container">
    
  </div>
</template>

<script>
export default {
  
}
</script>

2. Configure routing 

{
    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: '数据字典' }
      }
    ]
},

3. Implement data import 

<template>
  <div class="app-container">
    <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>

    <el-dialog title="数据字典导入" :visible.sync="dialogVisible" width="30%">
      <el-form>
        <el-form-item label="请选择Excel文件">
          <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>
    <el-table :data="list" border row-key="id" lazy :load="load">
    <el-table-column label="名称" align="left" prop="name" />
    <el-table-column label="编码" prop="dictCode" />
    <el-table-column label="值" align="left" prop="value" />
</el-table>
  </div>
  
</template>

<script>
import dictApi from '@/api/core/dict'
export default {



  created() {
    console.log("123231231")
    this.fetchData()
},


  // 定义数据
  data() {
    
    return {
     
      dialogVisible: false, //文件上传对话框是否显示
      BASE_API: process.env.VUE_APP_BASE_API ,//获取后端接口地址
      list: [],//数据字典列表
    }
  },
  

  methods: {
    // 调用api层获取数据库中的数据
fetchData() {
  dictApi.listByParentId(1).then(response => {
     console.log("8888888888")
    this.list = response.data.list
  })
},
//延迟加载子节点
getChildren(row, treeNode, resolve) {
  dictApi.listByParentId(row.id).then(response => {
    //负责将子节点数据展示在展开的列表中  
    resolve(response.data.list)
  })
},
    // 上传多于一个文件时
    fileUploadExceed() {
      this.$message.warning('只能选取一个文件')
    },

//上传成功回调
fileUploadSuccess(response) {
    if (response.code === 0) {
        this.$message.success('数据导入成功')
        this.dialogVisible = false
        this.fetchData()
    } else {
        this.$message.error(response.message)
    }
},

    //上传失败回调
    fileUploadError(error) {
      this.$message.error('数据导入失败')
    },
    //Excel数据导出
exportData() {
    window.location.href = this.BASE_API + '/admin/core/dict/export'
},
//加载节点
load(tree,treeNode,resolve){
    //获取数据
    dictApi.listByParentId(tree.id).then(response => {
    resolve(response.data.list)
  })
  }
  }

}

</script>

Excel data batch export 

1. Service layer parses Excel data

Interface: DictService

List<ExcelDictDTO> listDictData();

Implementation: DictServiceImpl 

@Override
public List<ExcelDictDTO> listDictData() {

    List<Dict> dictList = baseMapper.selectList(null);
    //创建ExcelDictDTO列表,将Dict列表转换成ExcelDictDTO列表
    ArrayList<ExcelDictDTO> excelDictDTOList = new ArrayList<>(dictList.size());
    dictList.forEach(dict -> {

        ExcelDictDTO excelDictDTO = new ExcelDictDTO();
        BeanUtils.copyProperties(dict, excelDictDTO);
        excelDictDTOList.add(excelDictDTO);
    });
    return excelDictDTOList;
}

 2. The Controller layer receives client requests

@ApiOperation("Excel数据的导出")
@GetMapping("/export")
public void export(HttpServletResponse response){
    
    try {
        // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
        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);
    }    
}

Second, the front-end call

1. Add an export button to the page

<el-button
    @click="exportData"
    type="primary"
    size="mini"
    icon="el-icon-upload2" >导出Excel</el-button>

2. Add export method 

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

Data dictionary list display 

1. Back-end interface

1. Add attributes to the entity class

Add properties to Dict

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

2. The Service layer implements data query

Interface: DictService

List<Dict> listByParentId(Long parentId);

Implementation: DictServiceImpl 

@Override
public List<Dict> listByParentId(Long parentId) {
    List<Dict> dictList = baseMapper.selectList(new QueryWrapper<Dict>().eq("parent_id", parentId));
    dictList.forEach(dict -> {
        //如果有子节点,则是非叶子节点
        boolean hasChildren = this.hasChildren(dict.getId());
        dict.setHasChildren(hasChildren);
    });
    return dictList;
}

/**
     * 判断该节点是否有子节点
     */
private boolean hasChildren(Long id) {
    QueryWrapper<Dict> queryWrapper = new QueryWrapper<Dict>().eq("parent_id", id);
    Integer count = baseMapper.selectCount(queryWrapper);
    if(count.intValue() > 0) {
        return true;
    }
    return false;
}

3. The Controller layer receives front-end requests 

@ApiOperation("根据上级id获取子节点数据列表")
@GetMapping("/listByParentId/{parentId}")
public R listByParentId(
    @ApiParam(value = "上级节点id", required = true)
    @PathVariable Long parentId) {
    List<Dict> dictList = dictService.listByParentId(parentId);
    return R.ok().data("list", dictList);
}

Second, the front-end call

1、api

Create src/api/core/dict.js

import request from '@/utils/request'
export default {
  listByParentId(parentId) {
    return request({
      url: `/admin/core/dict/listByParentId/${parentId}`,
      method: 'get'
    })
  }
}

2. Component script

define data

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

life cycle function 

created() {
    this.fetchData()
},

how to get data 

import dictApi from '@/api/core/dict'
// 调用api层获取数据库中的数据
fetchData() {
  dictApi.listByParentId(1).then(response => {
    this.list = response.data.list
  })
},

//延迟加载子节点
getChildren(row, treeNode, resolve) {
  dictApi.listByParentId(row.id).then(response => {
    //负责将子节点数据展示在展开的列表中  
    resolve(response.data.list)
  })
},

3. Component template 

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

4. Process optimization

Refresh the data list of the page after data import

//上传成功回调
fileUploadSuccess(response) {
    if (response.code === 0) {
        this.$message.success('数据导入成功')
        this.dialogVisible = false
        this.fetchData()
    } else {
        this.$message.error(response.message)
    }
},

unusual today 

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

There may be two problems:
1. The startup class is not written:
2. Although the startup class is written, the package where the startup class is located is not in the same root directory as the unit test package.
Such as: one is in cn.xxxx.cmcc, the other is in cn.xxxxx, they are not in the same directory, so it is reported that the startup class cannot be found:
put it in the same package directory to solve this problem.

Summary: The test class of the unit test must be in the same root directory as the startup class.
 

Guess you like

Origin blog.csdn.net/m0_62436868/article/details/126697488