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

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

作者: 阿叔有话说 | 来源:发表于2020-08-10 23:55 被阅读0次

    概述

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

    UI图

    image
    api资源

    功能设计

    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模板下载

    1. 客户端请求文件时,服务端查询模板文件,如存在模板文件则直接通过文件流(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);
        }
    
    1. 生成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);
        }
    };
    
    1. 关于前端下载文件
      前端下载文件多种方式均可实现,第一种通过请求静态文件方式获取下载(直接通过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数据批量导入资源

    1. 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,
                },
            ];
        }
    
    1. 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);
            }
        }
    
    

    结言

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

    资源

    github(前端)
    github(后端)

    在线地址BSPv1.1

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/imosdktx.html