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
核心功能
-
用户管理
- 学生、教师、管理员三种角色
- 基于JWT的登录认证
- 权限控制
-
请假管理
- 学生提交请假申请
- 教师审批请假单
- 请假记录查询
- 请假统计分析
-
系统管理
- 用户信息管理
- 角色权限配置
- 系统日志记录
核心代码示例
后端请假申请处理
@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(权限表)
项目亮点
- 采用前后端分离架构,提高开发效率
- 使用Redis缓存提升系统性能
- 实现基于RBAC的权限控制
- 统一异常处理和响应格式
- 完善的日志记录系统
项目部署
-
环境要求
- JDK 1.8+
- Maven 3.6+
- Node.js 14+
- MySQL 8.0
- Redis
-
部署步骤
- 导入数据库脚本
- 配置后端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"
}
}
六、注意事项
- 密码加密存储,使用BCrypt加密算法
- Token过期时间合理设置
- 敏感操作需要记录日志
- 定期清理过期Token
- 防止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"
}
四、注意事项
- 请假时间不能重叠
- 附件上传需要限制文件大小和类型
- 统计数据建议使用缓存
- 支付接口需要做幂等性处理
- 重要操作需要记录操作日志
五、优化建议
- 使用Redis缓存热点数据
- 大量统计查询考虑使用ES
- 添加请假审批工作流
- 接入消息推送服务
- 导出功能支持异步处理
系统管理模块详细设计
一、数据库设计
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. 日志清理策略
- 定时清理
- 分表存储
- 归档处理
六、优化建议
-
权限管理优化
- 实现动态权限控制
- 支持权限继承
- 添加数据权限控制
-
日志管理优化
- 使用ELK统一日志收集
- 添加日志告警功能
- 支持日志分析统计
-
缓存优化
- 实现多级缓存
- 添加缓存预热
- 优化缓存更新策略
-
性能优化
- 添加接口限流
- 优化SQL查询
- 使用多线程处理
-
安全优化
- 添加操作审计
- 敏感数据加密
- 防止SQL注入