node实现接口资源批量管理(含excel数据导入及模板下载)

193次阅读
没有评论

共计 8032 个字符,预计需要花费 21 分钟才能阅读完成。

概述

该功能是基于 BSPV1.0 版本中资源管理的扩展,在资源管理中添加接口资源,方便在对角色授权时做到更加精确的控制。第一层级为系统,第二层级为系统模块,第三层级为接口

UI 图

node 实现接口资源批量管理(含 excel 数据导入及模板下载)node 实现接口资源批量管理(含 excel 数据导入及模板下载)

功能设计

Api 资源表设计

  1. 考虑到系统中资源有限,在设计表时系统、模块、接口全部存储同一数据表,通过 type 进行区分,为方便构建系统模块接口间的关联关系,数据表中引入 parentId。
  @Entity({name: 'api_resource'})
  export class ApiResource {@PrimaryGeneratedColumn()
  id: number;

  @Column({length: 500})
  name: string;

  @Column('text', {nullable: true})
  desc: string;

  @Column()
  value: string;

  @Column({comment: '1: 系统,2:模块,3:接口 '})
  type: number;

  @Column()
  parentId: number;

  @Column({nullable: false})
  system: string;

  @Column({nullable: false})
  module: string;

  @Column()
  code: string;

  @Column({default: 0})
  isDelete: number;

  @Column({default: '', nullable: true})
  crateTime: string;

  @Column({default: '', nullable: true})
  updateTime: string;

  @Column({default: '', nullable: true})
  deleteTime: string;
  }

基本 CRDU

资源添加 / 编辑

    /**
     * 添加 api 资源
     * @param params
     */
    @Post('resource/add')
    public async createApiResource(@Body() params: CreateApiResourceDto): Promise<ResultData> {
        try {await this.apiResourceService.createApiResource(params);
            return new ResultData(MessageType.CREATE, null, true);
        } catch (e) {return new ResultData(MessageType.CREATE, null, false);
        }
    }

    /**
     * 更新 api 资源
     * @param params
     */
    @Post('resource/update')
    public async updateApiResource(@Body() params: UpdateApiResourceDto): Promise<ResultData> {
        try {await this.apiResourceService.updateApiResource(params);
            return new ResultData(MessageType.UPDATE, null, true);
        } catch (e) {return new ResultData(MessageType.UPDATE, null, false);
        }

    }
service 层
    /**
     * 添加 api 资源
     * @param params
     */
    public async createApiResource(params: CreateApiResourceDto): Promise<InsertResult> {
        try {
            return await this.apiResourceRepository
                .createQueryBuilder('r')
                .insert()
                .into(ApiResource)
                .values([{
                    name: params.name,
                    code: params.code,
                    type: params.type,
                    system: params.system,
                    isDelete: 0,
                    module: params.module,
                    crateTime: formatDate(),
                    value: params.value ? params.value : '',
                    desc: params.desc,
                    parentId: params.parentId }])
                .execute();} catch (e) {throw new ApiException(' 操作失败 ', ApiErrorCode.ROLE_LIST_FAILED, 200);
        }

    }

    /**
     * 更新 api 资源
     * @param params
     */
    public async updateApiResource(params: UpdateApiResourceDto): Promise<UpdateResult> {
        try {
            return await this.apiResourceRepository
                .createQueryBuilder('r')
                .update(ApiResource)
                .set({
                    name: params.name,
                    code: params.code,
                    type: params.type,
                    system: params.system,
                    isDelete: 0,
                    module: params.module,
                    updateTime: formatDate(),
                    value: params.value ? params.value : '',
                    desc: params.desc,
                    parentId: params.parentId })
                .where('id = :id', { id: params.id})
                .execute();} catch (e) {throw new ApiException(' 操作失败 ', ApiErrorCode.ROLE_LIST_FAILED, 200);
        }

excel 模板下载

  • 客户端请求文件时,服务端查询模板文件,如存在模板文件则直接通过文件流(res.type(\’application/vnd.openxmlformats\’))的形式返回前端,如果文件不存在则通过调用 excel 工具函数生成文件最后返回给前端。
  /**
   * 下载模板
   * @param res
   */
  @Get('template/download')
  public async downloadExcel(@Res() res: Response): Promise<any> {
      try {const filePath = join(__dirname, './apiResourceTemplate.xlsx');
          if (!existsSync(filePath)) {await this.createExcel();
          }
          res.type('application/vnd.openxmlformats');
          res.attachment(' 接口资源导入模板.xlsx');
          res.send(readFileSync(filePath));
      } catch (e) {return new ResultData(MessageType.FILEERROR,  false);
      }

  }
  /**
   * 创建 excel
   */
  public async createExcel(): Promise<any> {
      const params = {rows: this.apiResourceService.getRowDatas(), // 要导出的数据
          columns: this.apiResourceService.getColumnDatas(), // 列头信息
          sheetName: ' 导出示例 ', // 工作簿名称
          filePath: join(__dirname, './apiResourceTemplate.xlsx'),
      };
      return await this.apiResourceService.createExcel(params);
  }
  • 生成 excel 文件核心代码如下
  /**
  * 导出 excel 文件
  * @param {Array} columns  列头信息
  * @param {Array} rows  行数据
  * @param {String} sheetName  工作表名称
  * @param {path} savePath  文件保存路径
  * @param {path} style 设置每行高度
  */
  export const exportExcel = async (columns, rows, sheetName, savePath, style = { row: { height: 32} }) => {
  try {const workbook = new Excel.Workbook();
      const sheet = workbook.addWorksheet(sheetName);
      // 设置表头
      sheet.columns = columns.map(column => {return { header: column.name, width: column.size, key: column.key};
      });

      // 设置列的样式,对齐方式、自动换行等样式
      for (let i = 0; i < columns.length; i++) {const column = columns[i];
          if (!_.get(column, 'alignment', '')) {continue;}
          sheet.getColumn(column.key).alignment = column.alignment;
      }

      // 设置表头单元格样式与对齐方式
      const rowHeader = sheet.getRow(1);
      for (let i = 1; i <= sheet.columns.length; i++) {rowHeader.getCell(i).font = {name: 'Arial Black', size: 12, bold: false};
          rowHeader.getCell(i).alignment = {
              wrapText: false,
              horizontal: 'center',
          };
      }

      // 填充数据
      for (let index = 0; index < rows.length; index++) {const row = rows[index];
          const data = setRowVaules(columns, row, index, sheet, workbook);
          sheet.addRow(data);
          sheet.getRow(index + 2).height = style.row.height;
      }

      await workbook.xlsx.writeFile(savePath);
  } catch (error) {console.log(error);
  }
  };
  • 关于前端下载文件 前端下载文件多种方式均可实现,第一种通过请求静态文件方式获取下载(直接通过 url 地址请求)代码如下;第二种通过 ajax 异步请求数据,再转化为 Blob 类型下载, 该方案需要后端配合将返回数据格式设置为流(res.type(\’application/vnd.openxmlformats\’);)代码如下。

a. 第一种直接请求静态资源

http://img1.gtimg.com/chinanba/pics/hv1/124/191/2324/151166929.jpg

b. 异步模拟 a 标签点击

export const $getFile =  (url: any, params: any, server: any =  'wbw') => {axios.defaults.baseURL = getBaseUrl(server);
    return new Promise((resolve, reject) => {
        // 下载文件流必须将 responseType 设置为 arraybuffer
        axios.get(url, { params, responseType: 'arraybuffer',}).then((res: any) => {resolve(res); // 返回请求成功的数据 data
        }).catch((err: any) => {reject(err);
        });
    });
};
$getFile('apiResource/template/download', {})
                    .then(data => {
                        // 将数据流转化为 Blob
                        const url = window.URL.createObjectURL(new Blob([data], {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}))
                        let a = document.createElement('a')
                        // a.href = baseURL +  '/apiResource/template/download'
                        a.href = url
                        a.download = ' 资源导入模板 '
                        a.click()});

系统中全部接口会走网关进行用户身份认证,第一种方案无法通过手动方式写入 headers 故抛弃,选用第二种异步请求文件流的方式进行模板下载。

 前端请求文件流后进行下载是必须将 responseType 设置为 arraybuffer,否则下载的文件存在乱码,非常重要。

excel 数据批量导入资源

  • controller 层通过 FileInterceptor 中间拿到二进制数据流格式的文件,然后通过 excel 工具 r 解析数据,解析数据源格式必须设置为 buffer。
  /**
   * 资源数据导入
   * @param res
   */
  @Post('template/import')
  @UseInterceptors(FileInterceptor('file'))
  public async importExcel(@UploadedFile() file): Promise<ResultData> {
      try {
          // excel 中列对应的字段映射
          const column = this.apiResourceService.getColumnDatas();
          const list = await this.apiResourceService.importExcel(column, file.buffer, true, 'buffer');
          return new ResultData(MessageType.GETLIST,  {data: [], count: list.length}, true);
      } catch (e) {return new ResultData(MessageType.FILEERROR,  false);
      }

  }
   /**
   * 设置 excel 列头信息
   * name 列名
   * type 类型
   * key 对应数据的 key
   * size 大小
   */
  public getColumnDatas() {
      return [
          {
              name: ' 接口名称 ',
              type: 'String',
              key: 'name',
              size: 20,
              index: 1,
          },
          {
              name: ' 编码 ',
              type: 'String',
              key: 'code',
              size: 20,
              index: 2,
          },
          {
              name: ' 所属系统 ',
              type: 'String',
              key: 'system',
              size: 20,
              index: 3,
          },
          {
              name: ' 所属模块 ',
              type: 'String',
              key: 'module',
              size: 20,
              index: 4,
          },
          {
              name: ' 属性值 ',
              type: 'String',
              key: 'value',
              size: 20,
              index: 5,
          },
          {
              name: ' 描述 ',
              type: 'String',
              key: 'desc',
              size: 20,
              index: 6,
          },
      ];
  }
  • service 层进行数据的过滤,将有效数据进行持久化。
  /**
   * 导入资源数据
   */
  public async importExcel(column, file, hasHeader, type) {const list: ApiResource[] = await importExcel(column, file, hasHeader, type);
      let modulesName: string[] = [];
      const moduleMap = new Map();
      const afterList: ApiResource[] = [];
      const apiResourceList: ApiResource[] = list.map((item: ApiResource, index: number) => {modulesName.push(item.module);
          return {
              ...item,
              crateTime: formatDate(),
              isDelete: 0,
              type: 3,
          };
      });
      modulesName = Array.from(new Set(modulesName));
      for (let i = 0; i < modulesName.length; i++) {const currentModule = await this.apiResourceRepository.findOne({code: modulesName[i]});
          if (currentModule) {moduleMap.set(modulesName[i], currentModule);
          }
      }
      apiResourceList.forEach((item: ApiResource) => {item.parentId = moduleMap.get(item.module) ?  moduleMap.get(item.module).id : null;
          item.system = moduleMap.get(item.module) ?  moduleMap.get(item.module).system : null;
          if (item.parentId && item.system) {afterList.push(item);
          }
      });
      try {
          await this.apiResourceRepository
              .createQueryBuilder('r')
              .insert()
              .into(ApiResource)
              .values(afterList)
              .execute();
          return afterList;
      } catch (e) {console.log(e)
          throw new ApiException(' 操作失败 ', ApiErrorCode.AUTHORITY_DELETE_FILED, 200);
      }
  }

结言

  • 资源管理中加入接口资源是为更加精确控制角色权限而设计的,后期在网管层面中会有有对应体现
    原文地址:http://blog.canyuegongzi.xyz/

资源

github(前端)
github(后端)

在线地址 BSPv1.1

正文完
 
评论(没有评论)