1. 项目概述:企业级部门管理系统开发入门
部门管理模块是企业级Web应用的基础功能之一,也是新手进入全栈开发的最佳练手项目。这个看似简单的列表查询功能,实际上涵盖了现代Web开发的完整技术链:前端页面渲染、后端API设计、数据库操作以及前后端数据交互。我见过不少初级开发者在这个"简单"功能上栽跟头——要么接口设计不合理导致后续扩展困难,要么分页查询性能低下,甚至出现SQL注入漏洞。
从技术选型角度看,当前主流方案可分为三类:传统服务端渲染(如JSP/Thymeleaf)、前后端分离(React/Vue + RESTful API)以及新兴的全栈框架(Next.js/Nuxt.js)。考虑到学习曲线和就业市场需求,本实战将采用最通用的前后端分离架构,前端使用Vue 3组合式API,后端选择Spring Boot 2.7 + MyBatis-Plus技术栈。这种组合既能体现现代Web开发的核心思想,又不会因过于新潮的技术增加学习负担。
提示:实际企业开发中,部门管理往往与权限系统强关联。虽然首日任务只实现基础查询,但数据结构设计需要预留扩展空间。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心需求分析与技术拆解
2.1 功能需求规格
完整的部门列表查询应包含以下核心功能点:
- 基础信息展示(部门ID、名称、负责人、状态等)
- 分页控制(每页条数、当前页码、总数统计)
- 多条件筛选(按部门名称模糊查询、按状态精确过滤)
- 排序支持(点击表头字段升降序排列)
- 操作列(查看详情、编辑、删除等按钮)
在企业级应用中,还需要考虑:
- 数据权限控制(如分公司只能查看本部门及下属部门)
- 接口防刷保护(列表接口添加限流策略)
- 敏感字段脱敏(如负责人手机号显示为138****1234)
2.2 技术架构设计
前端技术栈:
bash复制- Vue 3.2 + Composition API
- Element Plus 2.3(UI组件库)
- Axios 1.3(HTTP客户端)
- Pinia 2.0(状态管理)
后端技术栈:
bash复制- Spring Boot 2.7.6
- MyBatis-Plus 3.5.3(ORM框架)
- Lombok 1.18(代码简化)
- Hutool 5.8(工具库)
- Knife4j 3.0(API文档)
数据库设计:
sql复制CREATE TABLE `sys_dept` (
`dept_id` bigint NOT NULL AUTO_INCREMENT COMMENT '部门id',
`parent_id` bigint DEFAULT '0' COMMENT '父部门id',
`dept_name` varchar(30) NOT NULL COMMENT '部门名称',
`leader` varchar(20) DEFAULT NULL COMMENT '负责人',
`phone` varchar(11) DEFAULT NULL COMMENT '联系电话',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
`status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`dept_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门表';
3. 前端实现关键步骤
3.1 页面布局与组件设计
使用Element Plus构建响应式表格布局:
vue复制<template>
<div class="app-container">
<el-card shadow="never">
<!-- 搜索区域 -->
<div class="filter-container">
<el-input v-model="queryParams.deptName" placeholder="部门名称" style="width: 200px" />
<el-select v-model="queryParams.status" placeholder="状态" clearable>
<el-option label="启用" value="0" />
<el-option label="停用" value="1" />
</el-select>
<el-button type="primary" @click="handleQuery">搜索</el-button>
</div>
<!-- 表格区域 -->
<el-table
v-loading="loading"
:data="deptList"
row-key="deptId"
default-expand-all
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column prop="deptName" label="部门名称" width="200" />
<el-table-column prop="leader" label="负责人" width="120" />
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
{{ scope.row.status === '0' ? '启用' : '停用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button size="small" @click="handleUpdate(scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页控件 -->
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-card>
</div>
</template>
3.2 API请求封装
在src/api/dept.js中封装接口方法:
javascript复制import request from '@/utils/request'
export function listDept(query) {
return request({
url: '/system/dept/list',
method: 'get',
params: query
})
}
export function getDept(deptId) {
return request({
url: `/system/dept/${deptId}`,
method: 'get'
})
}
3.3 状态管理与业务逻辑
使用Pinia管理部门状态:
javascript复制import { defineStore } from 'pinia'
import { listDept } from '@/api/dept'
export const useDeptStore = defineStore('dept', {
state: () => ({
deptList: [],
loading: false,
queryParams: {
pageNum: 1,
pageSize: 10,
deptName: undefined,
status: undefined
},
total: 0
}),
actions: {
async getList() {
this.loading = true
try {
const { rows, total } = await listDept(this.queryParams)
this.deptList = rows
this.total = total
} finally {
this.loading = false
}
},
resetQuery() {
this.queryParams = {
pageNum: 1,
pageSize: 10,
deptName: undefined,
status: undefined
}
}
}
})
4. 后端实现关键步骤
4.1 控制器层设计
java复制@RestController
@RequestMapping("/system/dept")
@Api(tags = "部门管理")
public class DeptController {
@Autowired
private IDeptService deptService;
@GetMapping("/list")
@ApiOperation("获取部门列表")
public TableDataInfo<List<SysDept>> list(SysDept dept) {
startPage();
List<SysDept> list = deptService.selectDeptList(dept);
return getDataTable(list);
}
}
4.2 服务层实现
java复制@Service
public class DeptServiceImpl extends ServiceImpl<DeptMapper, SysDept> implements IDeptService {
@Override
public List<SysDept> selectDeptList(SysDept dept) {
LambdaQueryWrapper<SysDept> lqw = new LambdaQueryWrapper<>();
lqw.like(StringUtils.isNotBlank(dept.getDeptName()), SysDept::getDeptName, dept.getDeptName())
.eq(StringUtils.isNotBlank(dept.getStatus()), SysDept::getStatus, dept.getStatus())
.orderByAsc(SysDept::getParentId, SysDept::getOrderNum);
return baseMapper.selectList(lqw);
}
}
4.3 分页插件配置
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
5. 性能优化与安全加固
5.1 接口性能优化
- N+1查询问题解决:
java复制@Mapper
public interface DeptMapper extends BaseMapper<SysDept> {
@Select("SELECT d.*, u.nick_name as leader_name " +
"FROM sys_dept d " +
"LEFT JOIN sys_user u ON d.leader = u.user_id " +
"${ew.customSqlSegment}")
List<DeptVO> selectDeptListWithLeader(@Param(Constants.WRAPPER) Wrapper<SysDept> wrapper);
}
- Redis缓存应用:
java复制@Cacheable(value = "dept", key = "#deptId")
public SysDept getDeptById(Long deptId) {
return baseMapper.selectById(deptId);
}
5.2 安全防护措施
- SQL注入防护:
- 始终使用MyBatis-Plus的Wrapper构建查询条件
- 禁止直接拼接SQL语句
- XSS防护:
java复制@Bean
public FilterRegistrationBean<XssFilter> xssFilterRegistration() {
FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new XssFilter());
registration.addUrlPatterns("/*");
registration.setName("xssFilter");
return registration;
}
- 接口限流保护:
java复制@RateLimiter(value = 10, key = "'deptList:' + #dept.deptName")
@GetMapping("/list")
public TableDataInfo list(SysDept dept) {
// ...
}
6. 常见问题排查指南
6.1 前端常见问题
- 表格不显示数据:
- 检查Chrome开发者工具Network面板,确认接口是否成功返回
- 查看返回数据结构是否与组件预期一致
- 验证Pinia状态是否正常更新
- 分页控件失效:
- 确保total字段正确绑定
- 检查pageNum和pageSize是否与查询参数同步
- 验证@pagination事件处理逻辑
6.2 后端常见问题
- 分页查询失效:
java复制// 错误示例:startPage()调用在查询之后
List<SysDept> list = deptService.selectDeptList(dept);
startPage(); // 不会生效
// 正确顺序
startPage();
List<SysDept> list = deptService.selectDeptList(dept);
- 树形结构展示异常:
- 确保返回数据包含parentId字段
- 前端el-table需要配置row-key和tree-props
- 后端查询需按parentId排序
- MyBatis-Plus版本冲突:
xml复制<!-- 确保版本统一 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.5.3</version>
</dependency>
7. 项目扩展与进阶方向
- 多级部门树形展示:
javascript复制// 前端转换扁平数据为树形结构
function buildTree(list) {
const map = {}
const roots = []
list.forEach(item => {
map[item.deptId] = { ...item, children: [] }
})
list.forEach(item => {
if (item.parentId !== 0) {
map[item.parentId]?.children.push(map[item.deptId])
} else {
roots.push(map[item.deptId])
}
})
return roots
}
- 部门导入导出:
java复制@PostMapping("/export")
@ResponseExcel(name = "部门数据")
public List<SysDept> export(SysDept dept) {
return deptService.selectDeptList(dept);
}
@PostMapping("/importData")
public AjaxResult importData(@RequestPart("file") MultipartFile file) {
ExcelUtil<SysDept> util = new ExcelUtil<>(SysDept.class);
List<SysDept> deptList = util.importExcel(file.getInputStream());
deptService.importDept(deptList);
return success();
}
- 部门变更日志审计:
java复制@Aspect
@Component
public class DeptLogAspect {
@AfterReturning(pointcut = "execution(* com..dept.service..*(..)) && @annotation(log)",
returning = "result")
public void afterReturning(JoinPoint joinPoint, Log log, Object result) {
// 记录操作日志
}
}
在实际企业开发中,部门管理模块往往会演变为组织架构管理中心,需要与用户管理、角色权限、数据权限等模块深度集成。建议在完成基础功能后,继续实现以下进阶特性:
- 部门负责人自动关联用户角色
- 部门数据权限过滤(如只能查看本部门及下属部门数据)
- 部门变更历史版本对比
- 部门合并与拆分业务逻辑
