当前位置: 首页 > article >正文

Java全栈项目:学生请假管理系统

项目介绍

学生请假管理系统是一个基于Spring Boot + Vue.js的全栈Web应用,实现了学生请假申请、教师审批、管理员管理等功能。本系统采用前后端分离架构,提供了直观的用户界面和完善的权限管理。

技术栈

后端

  • Spring Boot 2.7.x
  • Spring Security
  • MyBatis-Plus
  • MySQL 8.0
  • Redis

前端

  • Vue.js 3
  • Element Plus
  • Axios
  • Vuex
  • Vue Router

核心功能

  1. 用户管理

    • 学生、教师、管理员三种角色
    • 基于JWT的登录认证
    • 权限控制
  2. 请假管理

    • 学生提交请假申请
    • 教师审批请假单
    • 请假记录查询
    • 请假统计分析
  3. 系统管理

    • 用户信息管理
    • 角色权限配置
    • 系统日志记录

核心代码示例

后端请假申请处理

@Service
public class LeaveServiceImpl implements LeaveService {
    
    @Autowired
    private LeaveMapper leaveMapper;
    
    @Override
    public Result submitLeave(LeaveDTO leaveDTO) {
        Leave leave = new Leave();
        BeanUtils.copyProperties(leaveDTO, leave);
        leave.setStatus(LeaveStatus.PENDING.getCode());
        leave.setCreateTime(LocalDateTime.now());
        
        leaveMapper.insert(leave);
        return Result.success();
    }
}

前端请假表单组件

<template>
  <el-form :model="leaveForm" :rules="rules" ref="leaveFormRef">
    <el-form-item label="请假类型" prop="leaveType">
      <el-select v-model="leaveForm.leaveType">
        <el-option label="事假" value="1" />
        <el-option label="病假" value="2" />
        <el-option label="其他" value="3" />
      </el-select>
    </el-form-item>
    <el-form-item label="请假原因" prop="reason">
      <el-input type="textarea" v-model="leaveForm.reason" />
    </el-form-item>
    <!-- 其他表单项... -->
  </el-form>
</template>

数据库设计

主要包含以下几个核心表:

  • user(用户表)
  • leave_record(请假记录表)
  • role(角色表)
  • permission(权限表)

项目亮点

  1. 采用前后端分离架构,提高开发效率
  2. 使用Redis缓存提升系统性能
  3. 实现基于RBAC的权限控制
  4. 统一异常处理和响应格式
  5. 完善的日志记录系统

项目部署

  1. 环境要求

    • JDK 1.8+
    • Maven 3.6+
    • Node.js 14+
    • MySQL 8.0
    • Redis
  2. 部署步骤

    • 导入数据库脚本
    • 配置后端application.yml
    • 打包部署后端服务
    • 构建前端项目
    • 配置Nginx

总结与展望

本项目实现了学生请假管理的基本功能,采用主流技术栈开发,具有良好的可扩展性。未来可以考虑添加以下功能:

  • 微信小程序端
  • 请假审批流程的自定义配置
  • 接入第三方通知服务
  • 数据分析和可视化展示

用户管理模块详细设计

一、数据库设计

1. 用户表(sys_user)

CREATE TABLE `sys_user` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '密码',
  `real_name` varchar(50) COMMENT '真实姓名',
  `phone` varchar(11) COMMENT '手机号',
  `email` varchar(100) COMMENT '邮箱',
  `status` tinyint DEFAULT 1 COMMENT '状态:0-禁用,1-正常',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

2. 角色表(sys_role)

CREATE TABLE `sys_role` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `role_name` varchar(50) NOT NULL COMMENT '角色名称',
  `role_code` varchar(50) NOT NULL COMMENT '角色编码',
  `description` varchar(200) COMMENT '角色描述',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_role_code` (`role_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';

3. 用户角色关联表(sys_user_role)

CREATE TABLE `sys_user_role` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `role_id` bigint NOT NULL COMMENT '角色ID',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_role` (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';

二、核心代码实现

1. JWT工具类

public class JwtUtil {
    private static final String SECRET_KEY = "your-secret-key";
    private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000; // 24小时
    
    // 生成JWT token
    public static String generateToken(String username, Long userId, String roleCode) {
        Date expireDate = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        return Jwts.builder()
                .setSubject(username)
                .claim("userId", userId)
                .claim("roleCode", roleCode)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                .compact();
    }
    
    // 验证JWT token
    public static Claims verifyToken(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(SECRET_KEY)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            return null;
        }
    }
}

2. 登录认证Service

@Service
@Slf4j
public class AuthServiceImpl implements AuthService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Override
    public Result login(LoginDTO loginDTO) {
        // 查询用户
        User user = userMapper.selectByUsername(loginDTO.getUsername());
        if (user == null) {
            return Result.error("用户不存在");
        }
        
        // 密码校验
        if (!passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) {
            return Result.error("密码错误");
        }
        
        // 生成token
        String token = JwtUtil.generateToken(user.getUsername(), user.getId(), user.getRoleCode());
        
        // 返回用户信息
        LoginVO loginVO = new LoginVO();
        loginVO.setToken(token);
        loginVO.setUserInfo(UserConverter.toVO(user));
        
        return Result.success(loginVO);
    }
}

3. 权限拦截器

@Component
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 获取token
        String token = request.getHeader("Authorization");
        if (StringUtils.isEmpty(token)) {
            throw new BusinessException("未登录");
        }
        
        // 验证token
        Claims claims = JwtUtil.verifyToken(token);
        if (claims == null) {
            throw new BusinessException("token无效");
        }
        
        // 验证权限
        String roleCode = claims.get("roleCode", String.class);
        String requestUri = request.getRequestURI();
        if (!checkPermission(roleCode, requestUri)) {
            throw new BusinessException("无访问权限");
        }
        
        // 设置用户信息到上下文
        UserContext.setCurrentUser(claims);
        return true;
    }
}

三、权限控制设计

1. 角色权限划分

  • 学生(STUDENT)

    • 查看个人信息
    • 提交请假申请
    • 查看请假记录
  • 教师(TEACHER)

    • 查看个人信息
    • 审批请假申请
    • 查看学生请假记录
  • 管理员(ADMIN)

    • 用户管理
    • 角色管理
    • 系统配置
    • 查看所有记录

2. 权限注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireRole {
    String[] value() default {};
}

3. 权限注解使用示例

@RestController
@RequestMapping("/api/leave")
public class LeaveController {
    
    @PostMapping("/submit")
    @RequireRole("STUDENT")
    public Result submitLeave(@RequestBody LeaveDTO leaveDTO) {
        return leaveService.submitLeave(leaveDTO);
    }
    
    @PostMapping("/approve")
    @RequireRole("TEACHER")
    public Result approveLeave(@RequestBody ApproveDTO approveDTO) {
        return leaveService.approveLeave(approveDTO);
    }
}

四、安全配置

Spring Security配置

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .antMatchers("/api/auth/login").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

五、接口文档

1. 登录接口

  • 请求路径:/api/auth/login
  • 请求方式:POST
  • 请求参数:
{
    "username": "string",
    "password": "string"
}
  • 响应结果:
{
    "code": 200,
    "message": "success",
    "data": {
        "token": "string",
        "userInfo": {
            "id": "number",
            "username": "string",
            "realName": "string",
            "roleCode": "string"
        }
    }
}

2. 获取用户信息

  • 请求路径:/api/user/info
  • 请求方式:GET
  • 请求头:Authorization: Bearer token
  • 响应结果:
{
    "code": 200,
    "message": "success",
    "data": {
        "id": "number",
        "username": "string",
        "realName": "string",
        "phone": "string",
        "email": "string",
        "roleCode": "string"
    }
}

六、注意事项

  1. 密码加密存储,使用BCrypt加密算法
  2. Token过期时间合理设置
  3. 敏感操作需要记录日志
  4. 定期清理过期Token
  5. 防止SQL注入和XSS攻击

请假管理模块详细设计

一、数据库设计

1. 请假记录表(leave_record)

CREATE TABLE `leave_record` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `student_id` bigint NOT NULL COMMENT '学生ID',
  `teacher_id` bigint COMMENT '审批教师ID',
  `leave_type` tinyint NOT NULL COMMENT '请假类型:1-事假,2-病假,3-其他',
  `start_time` datetime NOT NULL COMMENT '开始时间',
  `end_time` datetime NOT NULL COMMENT '结束时间',
  `reason` varchar(500) NOT NULL COMMENT '请假原因',
  `status` tinyint DEFAULT 0 COMMENT '状态:0-待审批,1-已通过,2-已驳回',
  `remark` varchar(200) COMMENT '审批备注',
  `attachment_url` varchar(255) COMMENT '附件URL',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_student_id` (`student_id`),
  KEY `idx_teacher_id` (`teacher_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='请假记录表';

2. 请假费用表(leave_fee)

CREATE TABLE `leave_fee` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `leave_id` bigint NOT NULL COMMENT '请假记录ID',
  `fee_type` tinyint NOT NULL COMMENT '费用类型:1-请假费,2-证明费,3-其他',
  `amount` decimal(10,2) NOT NULL COMMENT '金额',
  `pay_status` tinyint DEFAULT 0 COMMENT '支付状态:0-未支付,1-已支付',
  `pay_time` datetime COMMENT '支付时间',
  `trade_no` varchar(64) COMMENT '交易流水号',
  PRIMARY KEY (`id`),
  KEY `idx_leave_id` (`leave_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='请假费用表';

二、核心代码实现

1. 请假申请Service

@Service
@Transactional
public class LeaveServiceImpl implements LeaveService {
    
    @Autowired
    private LeaveMapper leaveMapper;
    
    @Autowired
    private LeaveFeeMaper leaveFeeMapper;
    
    @Override
    public Result submitLeave(LeaveDTO leaveDTO) {
        // 参数校验
        validateLeaveParams(leaveDTO);
        
        // 创建请假记录
        LeaveRecord leave = new LeaveRecord();
        BeanUtils.copyProperties(leaveDTO, leave);
        leave.setStudentId(UserContext.getCurrentUserId());
        leave.setStatus(LeaveStatus.PENDING.getCode());
        leaveMapper.insert(leave);
        
        // 计算并创建费用记录
        LeaveFee leaveFee = calculateLeaveFee(leave);
        leaveFeeMapper.insert(leaveFee);
        
        return Result.success(leave.getId());
    }
    
    private void validateLeaveParams(LeaveDTO leaveDTO) {
        // 验证请假时间
        if (leaveDTO.getStartTime().isAfter(leaveDTO.getEndTime())) {
            throw new BusinessException("开始时间不能晚于结束时间");
        }
        
        // 验证是否存在重复请假
        boolean exists = leaveMapper.existsOverlap(
            UserContext.getCurrentUserId(), 
            leaveDTO.getStartTime(), 
            leaveDTO.getEndTime()
        );
        if (exists) {
            throw new BusinessException("该时间段已存在请假记录");
        }
    }
}

2. 请假审批Service

@Service
public class LeaveApprovalServiceImpl implements LeaveApprovalService {
    
    @Autowired
    private LeaveMapper leaveMapper;
    
    @Autowired
    private MessageService messageService;
    
    @Override
    @Transactional
    public Result approveLeave(ApprovalDTO approvalDTO) {
        // 获取请假记录
        LeaveRecord leave = leaveMapper.selectById(approvalDTO.getLeaveId());
        if (leave == null) {
            throw new BusinessException("请假记录不存在");
        }
        
        // 更新审批状态
        leave.setStatus(approvalDTO.getStatus());
        leave.setTeacherId(UserContext.getCurrentUserId());
        leave.setRemark(approvalDTO.getRemark());
        leaveMapper.updateById(leave);
        
        // 发送通知
        sendApprovalNotification(leave);
        
        return Result.success();
    }
    
    private void sendApprovalNotification(LeaveRecord leave) {
        String template = leave.getStatus() == LeaveStatus.APPROVED.getCode() ? 
            "您的请假申请已通过" : "您的请假申请被驳回";
        
        MessageDTO message = new MessageDTO();
        message.setUserId(leave.getStudentId());
        message.setTitle("请假审批通知");
        message.setContent(template + ",备注:" + leave.getRemark());
        messageService.sendMessage(message);
    }
}

3. 请假记录查询Service

@Service
public class LeaveQueryServiceImpl implements LeaveQueryService {
    
    @Autowired
    private LeaveMapper leaveMapper;
    
    @Override
    public PageResult<LeaveVO> queryLeaveRecords(LeaveQueryDTO queryDTO) {
        // 构建查询条件
        LambdaQueryWrapper<LeaveRecord> wrapper = new LambdaQueryWrapper<>();
        
        // 根据角色设置查询条件
        String roleCode = UserContext.getCurrentUserRole();
        if (RoleEnum.STUDENT.getCode().equals(roleCode)) {
            wrapper.eq(LeaveRecord::getStudentId, UserContext.getCurrentUserId());
        } else if (RoleEnum.TEACHER.getCode().equals(roleCode)) {
            wrapper.eq(LeaveRecord::getTeacherId, UserContext.getCurrentUserId());
        }
        
        // 设置查询参数
        wrapper.eq(queryDTO.getStatus() != null, LeaveRecord::getStatus, queryDTO.getStatus())
               .ge(queryDTO.getStartTime() != null, LeaveRecord::getStartTime, queryDTO.getStartTime())
               .le(queryDTO.getEndTime() != null, LeaveRecord::getEndTime, queryDTO.getEndTime())
               .orderByDesc(LeaveRecord::getCreateTime);
        
        // 分页查询
        Page<LeaveRecord> page = leaveMapper.selectPage(
            new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize()),
            wrapper
        );
        
        // 转换结果
        List<LeaveVO> records = page.getRecords().stream()
                .map(LeaveConverter::toVO)
                .collect(Collectors.toList());
                
        return new PageResult<>(records, page.getTotal());
    }
}

4. 请假统计Service

@Service
public class LeaveStatisticsServiceImpl implements LeaveStatisticsService {
    
    @Autowired
    private LeaveMapper leaveMapper;
    
    @Override
    public StatisticsVO getLeaveStatistics(StatisticsQueryDTO queryDTO) {
        StatisticsVO vo = new StatisticsVO();
        
        // 获取请假类型统计
        List<TypeStatisticsDTO> typeStats = leaveMapper.selectLeaveTypeStats(
            queryDTO.getStartDate(),
            queryDTO.getEndDate()
        );
        vo.setTypeStatistics(typeStats);
        
        // 获取请假时长统计
        List<DurationStatisticsDTO> durationStats = leaveMapper.selectLeaveDurationStats(
            queryDTO.getStartDate(),
            queryDTO.getEndDate()
        );
        vo.setDurationStatistics(durationStats);
        
        // 获取审批情况统计
        List<ApprovalStatisticsDTO> approvalStats = leaveMapper.selectApprovalStats(
            queryDTO.getStartDate(),
            queryDTO.getEndDate()
        );
        vo.setApprovalStatistics(approvalStats);
        
        return vo;
    }
}

5. 支付Service

@Service
public class LeavePayServiceImpl implements LeavePayService {
    
    @Autowired
    private LeaveFeeMapper leaveFeeMapper;
    
    @Autowired
    private PaymentClient paymentClient;
    
    @Override
    @Transactional
    public Result createPayment(Long leaveId) {
        // 获取费用信息
        LeaveFee leaveFee = leaveFeeMapper.selectByLeaveId(leaveId);
        if (leaveFee == null) {
            throw new BusinessException("费用记录不存在");
        }
        
        if (leaveFee.getPayStatus() == PayStatus.PAID.getCode()) {
            throw new BusinessException("该记录已支付");
        }
        
        // 创建支付订单
        PaymentDTO paymentDTO = new PaymentDTO();
        paymentDTO.setOrderNo(generateOrderNo());
        paymentDTO.setAmount(leaveFee.getAmount());
        paymentDTO.setSubject("请假申请费用");
        
        // 调用支付接口
        PaymentResult result = paymentClient.createPayment(paymentDTO);
        
        return Result.success(result);
    }
    
    @Override
    @Transactional
    public void handlePayCallback(PayCallbackDTO callbackDTO) {
        // 更新支付状态
        LeaveFee leaveFee = leaveFeeMapper.selectByOrderNo(callbackDTO.getOrderNo());
        leaveFee.setPayStatus(PayStatus.PAID.getCode());
        leaveFee.setPayTime(LocalDateTime.now());
        leaveFee.setTradeNo(callbackDTO.getTradeNo());
        leaveFeeMapper.updateById(leaveFee);
    }
}

三、接口文档

1. 提交请假申请

  • 请求路径:/api/leave/submit
  • 请求方式:POST
  • 请求参数:
{
    "leaveType": 1,
    "startTime": "2024-03-20 09:00:00",
    "endTime": "2024-03-21 17:00:00",
    "reason": "string",
    "attachmentUrl": "string"
}

2. 审批请假申请

  • 请求路径:/api/leave/approve
  • 请求方式:POST
  • 请求参数:
{
    "leaveId": "number",
    "status": 1,
    "remark": "string"
}

3. 查询请假记录

  • 请求路径:/api/leave/list
  • 请求方式:GET
  • 请求参数:
{
    "pageNum": 1,
    "pageSize": 10,
    "status": "number",
    "startTime": "string",
    "endTime": "string"
}

4. 获取请假统计

  • 请求路径:/api/leave/statistics
  • 请求方式:GET
  • 请求参数:
{
    "startDate": "string",
    "endDate": "string"
}

四、注意事项

  1. 请假时间不能重叠
  2. 附件上传需要限制文件大小和类型
  3. 统计数据建议使用缓存
  4. 支付接口需要做幂等性处理
  5. 重要操作需要记录操作日志

五、优化建议

  1. 使用Redis缓存热点数据
  2. 大量统计查询考虑使用ES
  3. 添加请假审批工作流
  4. 接入消息推送服务
  5. 导出功能支持异步处理

系统管理模块详细设计

一、数据库设计

1. 权限表(sys_permission)

CREATE TABLE `sys_permission` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `parent_id` bigint COMMENT '父权限ID',
  `name` varchar(50) NOT NULL COMMENT '权限名称',
  `permission_code` varchar(50) NOT NULL COMMENT '权限编码',
  `permission_type` tinyint NOT NULL COMMENT '类型:1-菜单,2-按钮',
  `path` varchar(200) COMMENT '路由路径',
  `component` varchar(200) COMMENT '组件路径',
  `icon` varchar(50) COMMENT '图标',
  `sort` int DEFAULT 0 COMMENT '排序',
  `status` tinyint DEFAULT 1 COMMENT '状态:0-禁用,1-正常',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_permission_code` (`permission_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';

2. 角色权限关联表(sys_role_permission)

CREATE TABLE `sys_role_permission` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `role_id` bigint NOT NULL COMMENT '角色ID',
  `permission_id` bigint NOT NULL COMMENT '权限ID',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_role_permission` (`role_id`,`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限关联表';

3. 系统日志表(sys_log)

CREATE TABLE `sys_log` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint COMMENT '操作用户ID',
  `username` varchar(50) COMMENT '操作用户名',
  `operation` varchar(50) COMMENT '操作描述',
  `method` varchar(200) COMMENT '请求方法',
  `params` text COMMENT '请求参数',
  `ip` varchar(64) COMMENT '操作IP',
  `status` tinyint COMMENT '操作状态:0-失败,1-成功',
  `error_msg` text COMMENT '错误信息',
  `operation_time` datetime COMMENT '操作时间',
  `execute_time` bigint COMMENT '执行时长(毫秒)',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_operation_time` (`operation_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统日志表';

二、核心代码实现

1. 用户管理Service

@Service
@Transactional
public class UserManageServiceImpl implements UserManageService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private UserRoleMapper userRoleMapper;
    
    @Override
    public Result addUser(UserDTO userDTO) {
        // 验证用户名是否存在
        if (userMapper.existsByUsername(userDTO.getUsername())) {
            throw new BusinessException("用户名已存在");
        }
        
        // 创建用户
        User user = new User();
        BeanUtils.copyProperties(userDTO, user);
        user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
        userMapper.insert(user);
        
        // 分配角色
        if (CollectionUtils.isNotEmpty(userDTO.getRoleIds())) {
            List<UserRole> userRoles = userDTO.getRoleIds().stream()
                .map(roleId -> new UserRole(user.getId(), roleId))
                .collect(Collectors.toList());
            userRoleMapper.batchInsert(userRoles);
        }
        
        return Result.success();
    }
    
    @Override
    public Result updateUser(UserDTO userDTO) {
        // 更新用户信息
        User user = userMapper.selectById(userDTO.getId());
        BeanUtils.copyProperties(userDTO, user, "password");
        userMapper.updateById(user);
        
        // 更新角色关联
        userRoleMapper.deleteByUserId(user.getId());
        if (CollectionUtils.isNotEmpty(userDTO.getRoleIds())) {
            List<UserRole> userRoles = userDTO.getRoleIds().stream()
                .map(roleId -> new UserRole(user.getId(), roleId))
                .collect(Collectors.toList());
            userRoleMapper.batchInsert(userRoles);
        }
        
        return Result.success();
    }
}

2. 角色权限管理Service

@Service
@Transactional
public class RolePermissionServiceImpl implements RolePermissionService {
    
    @Autowired
    private RoleMapper roleMapper;
    
    @Autowired
    private RolePermissionMapper rolePermissionMapper;
    
    @Override
    public Result updateRolePermissions(RolePermissionDTO dto) {
        // 验证角色是否存在
        Role role = roleMapper.selectById(dto.getRoleId());
        if (role == null) {
            throw new BusinessException("角色不存在");
        }
        
        // 更新角色权限
        rolePermissionMapper.deleteByRoleId(dto.getRoleId());
        if (CollectionUtils.isNotEmpty(dto.getPermissionIds())) {
            List<RolePermission> rolePermissions = dto.getPermissionIds().stream()
                .map(permissionId -> new RolePermission(dto.getRoleId(), permissionId))
                .collect(Collectors.toList());
            rolePermissionMapper.batchInsert(rolePermissions);
        }
        
        // 清除权限缓存
        clearPermissionCache(dto.getRoleId());
        
        return Result.success();
    }
    
    @Override
    public List<PermissionVO> getRolePermissions(Long roleId) {
        // 获取所有权限
        List<Permission> allPermissions = permissionMapper.selectAll();
        
        // 获取角色已有权限
        List<Long> rolePermissionIds = rolePermissionMapper.selectPermissionIdsByRoleId(roleId);
        
        // 构建权限树
        return buildPermissionTree(allPermissions, rolePermissionIds);
    }
}

3. 系统日志AOP

@Aspect
@Component
@Slf4j
public class SysLogAspect {
    
    @Autowired
    private SysLogMapper sysLogMapper;
    
    @Around("@annotation(com.example.annotation.SysLog)")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        
        // 执行方法
        Object result = null;
        try {
            result = point.proceed();
        } catch (Exception e) {
            // 记录异常日志
            saveLog(point, beginTime, e);
            throw e;
        }
        
        // 记录正常日志
        saveLog(point, beginTime, null);
        
        return result;
    }
    
    private void saveLog(ProceedingJoinPoint point, long beginTime, Exception e) {
        SysLog sysLog = new SysLog();
        
        // 设置用户信息
        sysLog.setUserId(UserContext.getCurrentUserId());
        sysLog.setUsername(UserContext.getCurrentUsername());
        
        // 设置请求信息
        MethodSignature signature = (MethodSignature) point.getSignature();
        sysLog.setMethod(signature.getDeclaringTypeName() + "." + signature.getName());
        sysLog.setParams(JSON.toJSONString(point.getArgs()));
        sysLog.setIp(IpUtil.getIpAddr());
        
        // 设置注解描述
        SysLogAnnotation annotation = signature.getMethod().getAnnotation(SysLogAnnotation.class);
        sysLog.setOperation(annotation.value());
        
        // 设置执行时间
        sysLog.setOperationTime(LocalDateTime.now());
        sysLog.setExecuteTime(System.currentTimeMillis() - beginTime);
        
        // 设置异常信息
        if (e != null) {
            sysLog.setStatus(0);
            sysLog.setErrorMsg(e.getMessage());
        } else {
            sysLog.setStatus(1);
        }
        
        // 异步保存日志
        AsyncManager.me().execute(() -> sysLogMapper.insert(sysLog));
    }
}

4. 权限缓存Service

@Service
public class PermissionCacheServiceImpl implements PermissionCacheService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String PERMISSION_KEY = "system:permission:";
    
    @Override
    public Set<String> getUserPermissions(Long userId) {
        String key = PERMISSION_KEY + userId;
        
        // 从缓存获取
        Set<String> permissions = redisTemplate.opsForSet().members(key);
        if (CollectionUtils.isNotEmpty(permissions)) {
            return permissions;
        }
        
        // 从数据库获取
        permissions = permissionMapper.selectPermissionsByUserId(userId);
        
        // 放入缓存
        if (CollectionUtils.isNotEmpty(permissions)) {
            redisTemplate.opsForSet().add(key, permissions.toArray(new String[0]));
            redisTemplate.expire(key, 30, TimeUnit.MINUTES);
        }
        
        return permissions;
    }
    
    @Override
    public void clearUserPermissions(Long userId) {
        redisTemplate.delete(PERMISSION_KEY + userId);
    }
}

三、接口文档

1. 用户管理接口

1.1 新增用户
  • 请求路径:/api/system/user/add
  • 请求方式:POST
  • 请求参数:
{
    "username": "string",
    "password": "string",
    "realName": "string",
    "phone": "string",
    "email": "string",
    "roleIds": [1, 2]
}
1.2 修改用户
  • 请求路径:/api/system/user/update
  • 请求方式:PUT
  • 请求参数:
{
    "id": "number",
    "realName": "string",
    "phone": "string",
    "email": "string",
    "status": "number",
    "roleIds": [1, 2]
}

2. 角色权限接口

2.1 获取角色权限
  • 请求路径:/api/system/role/permissions/{roleId}
  • 请求方式:GET
  • 响应结果:
{
    "code": 200,
    "data": [{
        "id": "number",
        "name": "string",
        "permissionCode": "string",
        "permissionType": "number",
        "children": []
    }]
}
2.2 更新角色权限
  • 请求路径:/api/system/role/permissions
  • 请求方式:PUT
  • 请求参数:
{
    "roleId": "number",
    "permissionIds": [1, 2, 3]
}

3. 系统日志接口

3.1 查询操作日志
  • 请求路径:/api/system/log/list
  • 请求方式:GET
  • 请求参数:
{
    "pageNum": 1,
    "pageSize": 10,
    "username": "string",
    "operation": "string",
    "startTime": "string",
    "endTime": "string"
}

四、权限管理设计

1. 权限类型

  • 菜单权限:用于控制菜单的显示/隐藏
  • 按钮权限:用于控制按钮的显示/隐藏
  • 数据权限:用于控制数据访问范围

2. 权限控制方式

  • 注解控制:使用@RequirePermission注解
  • 前端控制:使用v-permission指令
  • 后端控制:使用权限拦截器

3. 权限缓存策略

  • 用户权限缓存
  • 角色权限缓存
  • 菜单权限缓存

五、日志管理设计

1. 日志类型

  • 操作日志:记录用户操作
  • 登录日志:记录登录情况
  • 异常日志:记录系统异常

2. 日志存储策略

  • 数据库存储
  • 文件存储
  • ELK存储

3. 日志清理策略

  • 定时清理
  • 分表存储
  • 归档处理

六、优化建议

  1. 权限管理优化

    • 实现动态权限控制
    • 支持权限继承
    • 添加数据权限控制
  2. 日志管理优化

    • 使用ELK统一日志收集
    • 添加日志告警功能
    • 支持日志分析统计
  3. 缓存优化

    • 实现多级缓存
    • 添加缓存预热
    • 优化缓存更新策略
  4. 性能优化

    • 添加接口限流
    • 优化SQL查询
    • 使用多线程处理
  5. 安全优化

    • 添加操作审计
    • 敏感数据加密
    • 防止SQL注入

http://www.kler.cn/a/442235.html

相关文章:

  • Java定时任务不明原因挂掉(定时任务挂掉)以及建议
  • Redis系列之底层数据结构字典Dict
  • 第8篇:从入门到精通:掌握Python异常处理
  • 测试工程师的linux 命令学习(持续更新中)
  • Kotlin语言的数据库交互
  • 接口测试自动化实战(超详细的)
  • C++并发与多线程(高级函数async)
  • [每周一更]-(第127期):Go新项目-Gin中使用超时中间件实战(11)
  • 【深度学习基础】Windows实时查看GPU显存占用、功耗、进程状态
  • USB-A/C 2in1接口的未来应用前景分析
  • JAVA入门:使用IDE开发
  • 多模态检索增强生成
  • HarmonyOS 实时监听与获取 Wi-Fi 信息
  • 解锁Vue组件的奇妙世界
  • 【YashanDB知识库】数据库一主一备部署及一主两备部署时,主备手动切换方法及自动切换配置
  • 算法,递归和迭代
  • 交换机堆叠和集群
  • 线性池学习
  • vue登录成功之后的token处理
  • 【JS/TS鼠标气泡跟随】文本提示 / 操作提示
  • access数据库代做/mysql代做/Sql server数据库代做辅导设计服务
  • Jackson @JsonRootName 注解
  • Python | 虚拟环境04 - Qt Creator设置Python虚拟环境
  • HarmonyOS Next开发工具DevEco Studio介绍:ASan与TSan检测根治你的C++恐惧症
  • 使用k6进行kafka负载测试
  • 允许某段网络访问Linux服务器上的MariaDB