Proyecto de microservicio: Shang Rongbao (17) (construcción de back-end: diccionario de datos)

Abandona la fantasía, reconoce la realidad, prepárate para luchar

 necesitar

1. ¿Qué es un diccionario de datos?

¿Qué es un diccionario de datos? El diccionario de datos se encarga de gestionar los datos clasificados o algunos datos fijos de uso común en el sistema, tales como: datos de vinculación de tres niveles provinciales y urbanos, datos étnicos, datos de la industria, datos de educación, etc. El diccionario de datos nos ayuda a obtener y aplicar estos datos generales.

En segundo lugar, el diseño del diccionario de datos.

  • parent_id: ID de padre, construye la relación entre padre y padre a través de id y parent_id, por ejemplo: si queremos obtener todos los datos de la industria, solo necesitamos consultar los datos de parent_id=20000
  • nombre: nombre, por ejemplo: complete la información del usuario, debemos seleccionar la etiqueta para seleccionar la nacionalidad, "Nacionalidad Han" es el nombre del diccionario de datos
  • valor: valor, por ejemplo: complete la información del usuario, queremos seleccionar la etiqueta para seleccionar la nacionalidad, "1" (la identificación de la nacionalidad Han) es el valor del diccionario de datos
  • dict_code: codificación, la codificación es personalizada y globalmente única, por ejemplo: si queremos obtener datos de la industria, podemos obtenerlos a través de parent_id, pero parent_id es incierto, por lo que podemos obtener datos de la industria de acuerdo con el código

Esta configuración evita el exceso prolongado de varias tablas

 Importación por lotes de datos de Excel

1. Agregar dependencias

Agregue las siguientes dependencias al núcleo

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

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

2. Crear una clase de entidad de Excel 

@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. Crea un oyente 

@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. Inserción por lotes de la capa Mapper

Interfaz: DictMapper

void importData(InputStream inputStream);

Implementación: 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");
}

Nota: aquí se agrega el manejo de transacciones, rollbackFor = RuntimeException.class de forma predeterminada. Puede garantizar que después de que ocurra un error o una situación anormal, se pueda revertir en el tiempo. Evita datos incorrectos

6. La capa del controlador recibe cargas de clientes

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. Agregue la configuración de lanzamiento del mapeador

Nota: Debido a que todos los archivos de recursos en el directorio src/main/java del proyecto maven no se publican en el directorio de destino de manera predeterminada, debemos agregar la configuración de publicación del archivo de configuración xml en pom.xml

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

En segundo lugar, la llamada de front-end

1. Crear componentes de página

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

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

<script>
export default {
  
}
</script>

2. Configurar el enrutamiento 

{
    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. Implementar la importación de datos 

<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>

Exportación por lotes de datos de Excel 

1. La capa de servicio analiza los datos de Excel

Interfaz: DictService

List<ExcelDictDTO> listDictData();

Implementación: 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. La capa del controlador recibe las solicitudes de los clientes

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

En segundo lugar, la llamada de front-end

1. Agregue un botón de exportación a la página

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

2. Agregar método de exportación 

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

Visualización de la lista del diccionario de datos 

1. Interfaz de fondo

1. Agregar atributos a la clase de entidad

Agregar propiedades a Dict

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

2. La capa de servicio implementa la consulta de datos

Interfaz: DictService

List<Dict> listByParentId(Long parentId);

Implementación: 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. La capa del controlador recibe solicitudes de front-end 

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

En segundo lugar, la llamada de front-end

1 API

Crear 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. Guión de componente

definir datos

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

función del ciclo de vida 

created() {
    this.fetchData()
},

como obtener datos 

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. Plantilla de componentes 

<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. Optimización de procesos

Actualizar la lista de datos de la página después de la importación de datos

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

inusual hoy 

 java.lang.IllegalStateException: no se puede encontrar una @SpringBootConfiguration, debe usar

Puede haber dos problemas:
1. La clase de inicio no está escrita:
2. Aunque la clase de inicio está escrita, el paquete donde se encuentra la clase de inicio no está en el mismo directorio raíz que el paquete de prueba unitaria.
Por ejemplo: uno está en cn.xxxx.cmcc, el otro está en cn.xxxxx, no están en el mismo directorio, por lo que se informa que no se puede encontrar la clase de inicio: colóquelo
en el mismo directorio del paquete para resolver esto problema.

Resumen: la clase de prueba de la prueba unitaria debe estar en el mismo directorio raíz que la clase de inicio.
 

Supongo que te gusta

Origin blog.csdn.net/m0_62436868/article/details/126697488
Recomendado
Clasificación