概述
该功能是基于BSPV1.0版本中资源管理的扩展,在资源管理中添加接口资源,方便在对角色授权时做到更加精确的控制。第一层级为系统,第二层级为系统模块,第三层级为接口
UI图
功能设计
Api资源表设计
- 考虑到系统中资源有限,在设计表时系统、模块、接口全部存储同一数据表,通过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 {
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 {
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 {
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 {
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 {
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 {
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 {
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