共计 8032 个字符,预计需要花费 21 分钟才能阅读完成。
内容目录
概述
该功能是基于 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<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 | |
*/ | |
) | |
public async importExcel( 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/
资源
正文完