今天咱们来聊聊一个外卖系统后台管理中的核心功能模块——员工管理。作为后端开发者,我最近刚完成了一个名为《苍穹外卖》的项目,其中员工管理模块涉及了增删改查等基础但至关重要的功能。这个模块看似简单,但在实际开发过程中会遇到不少值得注意的技术细节。
员工管理系统是任何企业级应用的基础组件,特别是在外卖这种高频业务场景下,对员工账号的高效管理直接影响到运营效率。我们将使用Spring Boot+MyBatis这套经典组合来实现,数据库选用MySQL,这也是目前Java后端开发中最常见的技术栈之一。
新增员工是管理系统中最基础的功能,但也是最容易出问题的环节之一。从业务角度看,我们需要收集员工的基本信息:姓名、用户名、手机号、性别、身份证号等。这些字段看似简单,但在设计时需要考虑以下几个关键点:
在数据库设计上,我们的员工表(employee)主要包含以下字段:
sql复制CREATE TABLE `employee` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(32) COLLATE utf8mb4_bin NOT NULL COMMENT '姓名',
`username` varchar(32) COLLATE utf8mb4_bin NOT NULL COMMENT '用户名',
`password` varchar(64) COLLATE utf8mb4_bin NOT NULL COMMENT '密码',
`phone` varchar(11) COLLATE utf8mb4_bin NOT NULL COMMENT '手机号',
`sex` varchar(2) COLLATE utf8mb4_bin NOT NULL COMMENT '性别',
`id_number` varchar(18) COLLATE utf8mb4_bin NOT NULL COMMENT '身份证号',
`status` int NOT NULL DEFAULT '1' COMMENT '状态 0:禁用,1:正常',
`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 `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
前端表单需要包含以下字段:
建议使用Vue+Element UI实现,通过el-form组件进行表单校验:
vue复制<el-form :model="employeeForm" :rules="rules" ref="formRef">
<el-form-item label="姓名" prop="name">
<el-input v-model="employeeForm.name"></el-input>
</el-form-item>
<!-- 其他表单项 -->
</el-form>
Controller层:
java复制@RestController
@RequestMapping("/admin/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@PostMapping
public Result<String> save(@RequestBody EmployeeDTO employeeDTO) {
employeeService.save(employeeDTO);
return Result.success();
}
}
Service层关键逻辑:
java复制@Override
public void save(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
BeanUtils.copyProperties(employeeDTO, employee);
// 设置初始密码(身份证后6位)
employee.setPassword(DigestUtils.md5DigestAsHex(
employeeDTO.getIdNumber().substring(12).getBytes()));
employee.setStatus(StatusConstant.ENABLE);
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
// 从线程局部变量中获取当前操作人ID
employee.setCreateUser(BaseContext.getCurrentId());
employee.setUpdateUser(BaseContext.getCurrentId());
employeeMapper.insert(employee);
}
用户名重复问题:
java复制Employee emp = employeeMapper.getByUsername(employeeDTO.getUsername());
if (emp != null) {
throw new UsernameDuplicateException("用户名已存在");
}
手机号格式校验:
java复制if (!Pattern.matches("^1[3-9]\\d{9}$", employeeDTO.getPhone())) {
throw new PhoneFormatException("手机号格式不正确");
}
密码加密存储:
提示:在实际项目中,建议使用更安全的加密方式如BCryptPasswordEncoder,而不是简单的MD5
分页查询是管理系统中最常用的功能之一。我们的需求是:
使用MyBatis-Plus的分页插件:
java复制@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
Controller:
java复制@GetMapping("/page")
public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO) {
PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
return Result.success(pageResult);
}
Service:
java复制@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
Page<Employee> page = new Page<>(
employeePageQueryDTO.getPage(),
employeePageQueryDTO.getPageSize());
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotEmpty(employeePageQueryDTO.getName()),
Employee::getName, employeePageQueryDTO.getName());
if (employeePageQueryDTO.getBeginTime() != null
&& employeePageQueryDTO.getEndTime() != null) {
queryWrapper.between(Employee::getCreateTime,
employeePageQueryDTO.getBeginTime(),
employeePageQueryDTO.getEndTime());
}
employeeMapper.selectPage(page, queryWrapper);
return new PageResult(page.getTotal(), page.getRecords());
}
前端展示时,数据库中的datetime类型直接返回会导致格式不符合要求,如:
code复制"createTime": "2023-05-10T15:30:45"
而我们需要的是:
code复制"createTime": "2023-05-10 15:30:45"
方式一:使用@JsonFormat注解
java复制@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
方式二:全局配置(推荐)
yaml复制spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
注意:LocalDateTime类型需要添加jackson-datatype-jsr310依赖
账号状态管理是系统安全的重要环节。我们需要:
使用自定义注解+拦截器实现:
java复制@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AdminOnly {
}
拦截器实现:
java复制public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
Long empId = JwtUtil.getEmpId(request);
Employee employee = employeeService.getById(empId);
if (employee == null || employee.getStatus() == StatusConstant.DISABLE) {
throw new AccountLockedException("账号已禁用");
}
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
AdminOnly adminOnly = handlerMethod.getMethodAnnotation(AdminOnly.class);
if (adminOnly != null && !RoleConstant.ADMIN.equals(employee.getRole())) {
throw new PermissionDeniedException("无权限操作");
}
}
return true;
}
Controller:
java复制@PostMapping("/status/{status}")
@AdminOnly
public Result<String> updateStatus(@PathVariable Integer status, Long id) {
employeeService.updateStatus(status, id);
return Result.success();
}
Service:
java复制@Override
public void updateStatus(Integer status, Long id) {
// 不能禁用自己
Long currentId = BaseContext.getCurrentId();
if (currentId.equals(id)) {
throw new OperationNotAllowedException("不能禁用当前登录账号");
}
Employee employee = Employee.builder()
.id(id)
.status(status)
.updateTime(LocalDateTime.now())
.updateUser(currentId)
.build();
employeeMapper.updateById(employee);
}
编辑功能需要实现:
Controller:
java复制@GetMapping("/{id}")
public Result<EmployeeVO> getById(@PathVariable Long id) {
EmployeeVO employeeVO = employeeService.getByIdWithVO(id);
return Result.success(employeeVO);
}
Service:
java复制@Override
public EmployeeVO getByIdWithVO(Long id) {
Employee employee = employeeMapper.selectById(id);
if (employee == null) {
throw new EmployeeNotFoundException("员工不存在");
}
EmployeeVO employeeVO = new EmployeeVO();
BeanUtils.copyProperties(employee, employeeVO);
return employeeVO;
}
Controller:
java复制@PutMapping
public Result<String> update(@RequestBody EmployeeDTO employeeDTO) {
employeeService.update(employeeDTO);
return Result.success();
}
Service:
java复制@Override
public void update(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
BeanUtils.copyProperties(employeeDTO, employee);
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(BaseContext.getCurrentId());
employeeMapper.updateById(employee);
}
提示:实际项目中建议使用动态SQL实现字段级更新,避免全字段更新带来的性能问题
分类模块通常包含以下组件:
sql复制CREATE TABLE `category` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`type` int DEFAULT NULL COMMENT '类型 1菜品分类 2套餐分类',
`name` varchar(32) COLLATE utf8mb4_bin NOT NULL COMMENT '分类名称',
`sort` int NOT NULL DEFAULT '0' COMMENT '顺序',
`status` int DEFAULT '1' COMMENT '分类状态 0:禁用,1:启用',
`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 `idx_category_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
java复制@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Category implements Serializable {
private Long id;
private Integer type;
private String name;
private Integer sort;
private Integer status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Long createUser;
private Long updateUser;
}
在实际开发中,我通常会使用MyBatis-Plus的代码生成器来快速生成基础CRUD代码,然后根据业务需求进行定制化开发。
在开发员工管理模块时,有几个特别值得注意的点:
密码安全问题:
权限控制:
性能优化:
异常处理:
这个模块虽然基础,但涵盖了后端开发的许多核心知识点。建议新手开发者可以从这个模块入手,逐步掌握Spring Boot开发的各项技能。在实际项目中,还需要考虑更多细节,如接口幂等性、数据一致性、缓存策略等。