前言
本系列主要通过实现一个后台管理系统作为切入点,帮助掘友熟悉nest
的开发套路
本系列作为前后端分离项目,主要讲解nest
相关知识,前端项目知识一笔带过
完整的示例代码在最后,sql文件在/doc
下的nest-study.sql
文件中
本章属于实战系列第三章,主要讲解部门模块和组织架构模块相关内容,包括
- 部门模块
CRUD
操作 - 组织架构执行位置移动时,添加
事务处理
上移、下移、置顶、置底
如何修改数据库数据子查询
构建组织部门树结构
预览
规则
- 本系列完全遵循
RestFul
风格进行开发,即Get
Post
Put
Delete
- 本系列的调用链
controller
-->service
-->三方服务
|数据库
- 本系列的三方服务被
消费
时,统一注入到nest
的IOC
中,统一风格;其他函数的使用直接调用即可
部门模块
表设计
CREATE TABLE `department` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(255) NOT NULL COMMENT '部门名称',
`status` int NOT NULL DEFAULT '1' COMMENT '状态 0 禁用; 1启用',
`avatar` varchar(255) DEFAULT NULL COMMENT '部门头像',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`create_user` bigint NOT NULL COMMENT '创建人',
`update_user` bigint NOT NULL COMMENT '修改人',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 COMMENT='部门信息表';
说明
- 部门模块和员工模块基本一致,大家参考开发就行
- 本项目部门模块存放在
src/department
下,这是gitee仓库demo/v5
组织架构
表设计
sql设计
CREATE TABLE `organization` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(255) DEFAULT NULL COMMENT '组织架构名',
`sort` int DEFAULT NULL COMMENT '组织架构顺序',
`d_id` bigint DEFAULT NULL COMMENT '部门ID',
`e_id` bigint DEFAULT NULL COMMENT '员工ID',
`p_id` bigint DEFAULT NULL COMMENT '父级ID',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`create_user` bigint NOT NULL COMMENT '创建人',
`update_user` bigint NOT NULL COMMENT '修改人',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8 COMMENT='组织架构表';
说明
- 组织架构表主要关注三个字段
d_id、e_id、p_id
,其中d_id、e_id
分别对应部门表和员工表
,主要用来做作表关联,p_id
表示当前组织架构属于哪个部门 - 剩余字段中规中矩
创建部门
代码
- 写入
src/organization/organization.controller.ts
@ApiOperation({ summary: '创建组织结构', }) @Post() create(@Body() organization: Organization) { return this.organizationService.create(organization); }
- 写入
src/organization/organization.service.ts
/** * * @param organization * @returns 新增组织结构 */ async create(organization: Organization) { // 设置默认sort 自增 let { maxSort } = await this.organizationRepository .createQueryBuilder('organization') .select('MAX(organization.sort)', 'maxSort') .getRawOne(); const _organization = classAssign(new Organization(), organization); if (!maxSort) { maxSort = 1; } else { maxSort++; } _organization.sort = maxSort; return this.organizationRepository.save(_organization); } ```
说明
- 由于需要对组织结构进行位置移动操作,需要在新增组织结构的时候添加
sort
排序字段 - 如果当前表中没有
sort
默认填充1
,否则的话++
即可
返回树结构
代码
- 写入
src/organization/organization.controller.ts
@ApiOperation({ summary: '组织结构 - 🌲结构', }) @Get('tree') @isPublic() tree() { return this.organizationService.tree(); } @ApiOperation({ summary: '根据ID查询组织架构' }) @Get(':id') findOne(@Param('id') id: string) { return this.organizationService.findById(id); }
- 写入
src/organization/organization.service.ts
/** * * @returns 树结构 */ async tree() { return listToTree( await this.organizationRepository .createQueryBuilder('o1') .select() .leftJoinAndSelect('organization', 'o2', 'o1.id = o2.p_id') .where('o1.d_id IS NOT NULL') .orderBy('o1.sort', 'ASC') .getMany(), ); }
- 写入
src/common/utils/index.ts
/** * * @param data * @returns 列表转树结构 */ export function listToTree<T extends { id: string; pId: string }>(data: T[]) { const obj = {}; data.forEach((w) => (obj[w.id] = w)); type TParent = T & { children?: T[] }; const parentList: TParent[] = []; data.forEach((w) => { const parent: TParent = obj[w.pId]; if (parent) { parent.children = parent.children || []; parent.children.push(w); } else { parentList.push(w); } }); return parentList; }
说明
- 在
OrganizationService
中,通过子查询
的方式获取到组织架构信息 orderBy
设置asc
表示,按照sort
字段进行升序进行排序- 执行后的sql语句如下
- 然后只需要通过
listToTree
方法,把数组转化成树结构的数组
返回即可
删除部门
代码
- 写入
src/organization/organization.controller.ts
@ApiOperation({ summary: '删除部门' }) @Delete() delete(@Query('id') id: string) { return this.organizationService.delete(id); }
- 写入
src/organization/organization.service.ts
/** * * @param id * @returns 删除组织架构 */ async delete(id: string) { // 如果当前组织架构下存在客户或者组织结构,无法删除 const count = await this.organizationRepository.countBy({ pId: id, }); if (count > 0) { throw new CustomException('此部门组织架构下存在组织或者员工,无法删除'); } return !!(await this.organizationRepository.delete({ id })).affected; }
说明
- 如果部门中存在员工信息,无法删除(这个看具体的业务场景)
- 最后返回删除的状态即可
true
orfalse
置顶
代码
- 写入
src/organization/organization.controller.ts
@ApiOperation({ summary: '置顶', }) @Put('toTop') toTop(@Body('id') id: string) { return this.organizationService.toTop(id); }
- 写入
src/organization/organization.service.ts
/** * * @param id * @returns 置顶 */ @Transactional() async toTop(id: string) { // 查询到需要置顶的信息 const organization = await this.organizationRepository.findOneBy({ id }); if (!organization) { throw new CustomException('此部门数据不存在'); } // 查询同级上一条数据 const prev = await this.organizationRepository.findOneBy({ sort: LessThan(organization.sort), pId: organization.pId, }); if (!prev) { throw new CustomException('此部门已经处于置顶状态了'); } // 置顶 await this.organizationRepository .createQueryBuilder() .update(Organization) .set({ sort: () => 'sort + 1', }) .where('sort < :sort', { sort: organization.sort }) .execute(); await this.organizationRepository.update({ id }, { sort: 1 }); return true; }
说明
- 这块逻辑比较复杂
- 第一步查看当前数据
是否存在
- 然后判断当前数据是否是第一条,
已经置顶的数据不需要再次置顶
- 接下来就是置顶操作
思路如下
- 首先,需要获取需要置顶的数据POJO,将表中
sort
字段小于POJO顺序的所有数据的sort
增加1,再将POJO的sort
设置为1,最后在查询表时,按seq增序排列,就能看到置顶的效果
置底
代码
- 写入
src/organization/organization.controller.ts
@ApiOperation({ summary: '置底', }) @Put('toBottom') toBottom(@Body('id') id: string) { return this.organizationService.toBottom(id); }
- 写入
src/organization/organization.service.ts
/** * * @param id * @returns 置底 */ @Transactional() async toBottom(id: string) { // 查询需要置底的数据 const organization = await this.organizationRepository.findOneBy({ id }); // 查询同级下一条数据 const last = await this.organizationRepository.findOne({ where: { sort: MoreThan(organization.sort), pId: organization.pId, }, }); if (!last) { throw new CustomException('此部门已经处于最后了,无法下移'); } // 获取最大sort const maxOrganization = await this.organizationRepository.findOne({ where: {}, order: { sort: 'DESC', }, }); if (!maxOrganization) { throw new CustomException('xxx'); } // 置底 await this.organizationRepository .createQueryBuilder() .update(Organization) .set({ sort: () => 'sort - 1' }) .where('sort > :sort', { sort: organization.sort }) .execute(); await this.organizationRepository.update( { id: organization.id, }, { sort: maxOrganization.sort, }, ); return true; }
说明
- 整体思路大体和
置顶功能
一致 - 唯一有区别的就是置底功能的实现,
思路如下
- 首先,拿到
数据库
中sort最大的数据maxOrganization
,执行update
操作,将大于当前数据的数据库中sort
字段进行-1
操作,最后将maxOrganization
的sort
值赋值给当前数据,最后更新数据库即可
上移
代码
- 写入
src/organization/organization.controller.ts
@ApiOperation({ summary: '上移', }) @Put('moveUp') moveUp(@Body('id') id: string) { return this.organizationService.moveUp(id); }
- 写入
src/organization/organization.service.ts
/** * * @param id * @returns 上移 */ @Transactional() async moveUp(id: string) { const organization = await this.organizationRepository.findOneBy({ id }); if (!organization) { throw new CustomException('此部门id不存在'); } // 拿到同级上一条数据 const prev = await this.organizationRepository.findOne({ where: { sort: LessThan(organization.sort), pId: organization.pId, }, order: { id: 'DESC', }, }); if (!prev) { throw new CustomException('此部门已经置顶了,无法上移'); } // 交换 sort await this.organizationRepository.update( { id: organization.id }, { sort: prev.sort }, ); await this.organizationRepository.update( { id: prev.id }, { sort: organization.sort }, ); return true; }
下移
代码
-
写入
src/organization/organization.controller.ts
@ApiOperation({ summary: '下移', }) @Put('moveDown') movDown(@Body('id') id: string) { return this.organizationService.moveDown(id); }
-
写入
src/organization/organization.service.ts
/** * * @param id * @returns 下移 */ @Transactional() async moveDown(id: string) { const organization = await this.organizationRepository.findOneBy({ id }); if (!organization) { throw new CustomException('此部门id不存在'); } // 拿到同级下一条数据 const next = await this.organizationRepository.findOne({ where: { sort: MoreThan(organization.sort), pId: organization.pId, }, }); if (!next) { throw new CustomException('此部门已经处于最后了,无法下移'); } // 交换 sort await this.organizationRepository.update( { id: organization.id, }, { sort: next.sort, }, ); await this.organizationRepository.update( { id: next.id, }, { sort: organization.sort, }, ); return true; }
说明
- 下移就比较简单了,拿到下一条数据,交换两条
sort
的值即可
写在最后
-
本章主要讲解部门管理和组织架构模块,如有问题欢迎在评论区留言
-
nest
代码已经放在 gitee demo/v6分支 -
对
mysql
不熟悉的可以看下 前端玩转mysql和Nodejs连接Mysql 这俩篇文章