项目介绍
本文将详细介绍一个基于Java全栈技术开发的学生考勤请假一体化系统。该系统旨在为学校提供一个现代化的考勤管理解决方案,实现学生考勤记录、请假申请、审批流程的一体化管理。
技术栈
后端技术
- Spring Boot 2.6.x:核心框架
- Spring Security:安全框架
- MyBatis-Plus:ORM框架
- MySQL 8.0:数据库
- Redis:缓存服务
- JWT:用户认证
前端技术
- Vue 3:前端框架
- Element Plus:UI组件库
- Axios:HTTP客户端
- Vuex:状态管理
- Vue Router:路由管理
核心功能模块
用户管理模块
- 用户角色:管理员、教师、学生
- 账号管理:注册、登录、密码修改
- 权限控制:基于RBAC的权限管理
考勤管理模块
- 考勤记录:支持教师发起签到
- 考勤统计:按班级、学生、时间段统计
- 异常记录:迟到、早退、旷课标记
请假管理模块
- 请假申请:学生在线提交请假申请
- 审批流程:教师审批、管理员备案
- 请假记录:历史请假记录查询
系统管理模块
- 班级管理:班级信息维护
- 课程管理:课程信息维护
- 教师管理:教师信息维护
- 学生管理:学生信息维护
数据库设计
核心表结构
- user:用户信息表
- role:角色表
- permission:权限表
- attendance:考勤记录表
- leave_application:请假申请表
- class:班级信息表
- course:课程信息表
接口设计
RESTful API
- 用户接口:/api/user/**
- 考勤接口:/api/attendance/**
- 请假接口:/api/leave/**
- 系统管理接口:/api/system/**
系统架构
分层架构
- 表现层:处理HTTP请求响应
- 业务层:实现核心业务逻辑
- 持久层:数据库访问操作
- 通用层:工具类、配置类
项目结构
src
├── main
│ ├── java
│ │ └── com.example.attendance
│ │ ├── controller
│ │ ├── service
│ │ ├── mapper
│ │ ├── entity
│ │ ├── dto
│ │ ├── vo
│ │ ├── config
│ │ └── util
│ └── resources
│ ├── mapper
│ ├── static
│ └── application.yml
系统特点
高性能
- 采用Redis缓存提升系统响应速度
- MyBatis-Plus提供高效的数据库操作
- 前端组件懒加载优化页面加载速度
高可用
- 统一异常处理机制
- 操作日志记录
- 数据备份恢复机制
安全性
- Spring Security权限控制
- JWT令牌认证
- 密码加密存储
- SQL注入防护
部署方案
开发环境
- JDK 1.8+
- Maven 3.6+
- Node.js 14+
- MySQL 8.0
- Redis 6.0+
生产环境
- Nginx:反向代理服务器
- Docker:容器化部署
- Jenkins:持续集成部署
项目亮点
技术创新
- 采用最新的Spring Boot 2.6.x版本
- 使用Vue 3 Composition API
- 引入Element Plus最新组件库
业务创新
- 智能考勤提醒
- 请假审批自动化
- 考勤数据可视化分析
总结展望
本项目采用主流的Java全栈技术栈,实现了一个功能完整、性能优秀的学生考勤请假系统。系统具有良好的可扩展性和维护性,为学校管理提供了现代化的解决方案。
未来计划持续优化系统性能,增加移动端适配,引入人脸识别等新技术,提供更智能化的考勤解决方案。
一、用户管理模块的实现
数据库设计
用户表(sys_user)
CREATE TABLE sys_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
password VARCHAR(100) NOT NULL COMMENT '密码',
real_name VARCHAR(50) COMMENT '真实姓名',
email VARCHAR(100) COMMENT '邮箱',
phone VARCHAR(20) COMMENT '手机号',
avatar VARCHAR(200) COMMENT '头像URL',
status TINYINT DEFAULT 1 COMMENT '状态(0:禁用,1:正常)',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE INDEX idx_username(username)
) COMMENT '用户表';
角色表(sys_role)
CREATE TABLE sys_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '角色ID',
role_name VARCHAR(50) NOT NULL COMMENT '角色名称',
role_code VARCHAR(50) NOT NULL COMMENT '角色编码',
description VARCHAR(200) COMMENT '角色描述',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME ON UPDATE CURRENT_TIMESTAMP,
UNIQUE INDEX idx_role_code(role_code)
) COMMENT '角色表';
用户角色关联表(sys_user_role)
CREATE TABLE sys_user_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL COMMENT '用户ID',
role_id BIGINT NOT NULL COMMENT '角色ID',
UNIQUE INDEX idx_user_role(user_id, role_id)
) COMMENT '用户角色关联表';
权限表(sys_permission)
CREATE TABLE sys_permission (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '权限ID',
parent_id BIGINT COMMENT '父权限ID',
name VARCHAR(50) NOT NULL COMMENT '权限名称',
permission_code VARCHAR(50) NOT NULL COMMENT '权限编码',
path VARCHAR(200) COMMENT '路由地址',
component VARCHAR(200) COMMENT '组件路径',
type TINYINT COMMENT '类型(1:菜单,2:按钮)',
icon VARCHAR(50) COMMENT '图标',
sort_order INT DEFAULT 0 COMMENT '排序',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME ON UPDATE CURRENT_TIMESTAMP,
UNIQUE INDEX idx_permission_code(permission_code)
) COMMENT '权限表';
角色权限关联表(sys_role_permission)
CREATE TABLE sys_role_permission (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
role_id BIGINT NOT NULL COMMENT '角色ID',
permission_id BIGINT NOT NULL COMMENT '权限ID',
UNIQUE INDEX idx_role_permission(role_id, permission_id)
) COMMENT '角色权限关联表';
后端实现
实体类
@Data
@TableName("sys_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
private String realName;
private String email;
private String phone;
private String avatar;
private Integer status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
DTO类
@Data
public class LoginDTO {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
}
@Data
public class UserDTO {
private Long id;
private String username;
private String realName;
private String email;
private String phone;
private List<String> roleIds;
}
服务接口
public interface UserService extends IService<User> {
UserDTO login(LoginDTO loginDTO);
void register(UserDTO userDTO);
void updatePassword(Long userId, String oldPassword, String newPassword);
List<PermissionVO> getUserPermissions(Long userId);
}
服务实现
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
public UserDTO login(LoginDTO loginDTO) {
User user = baseMapper.selectOne(new QueryWrapper<User>()
.eq("username", loginDTO.getUsername()));
if (user == null || !passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) {
throw new BusinessException("用户名或密码错误");
}
if (user.getStatus() == 0) {
throw new BusinessException("账号已被禁用");
}
// 生成token
String token = jwtTokenUtil.generateToken(user);
// 转换并返回UserDTO
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
userDTO.setToken(token);
return userDTO;
}
// 其他方法实现...
}
控制器
@RestController
@RequestMapping("/api/user")
@Validated
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/login")
public Result<UserDTO> login(@RequestBody @Valid LoginDTO loginDTO) {
return Result.success(userService.login(loginDTO));
}
@PostMapping("/register")
public Result<?> register(@RequestBody @Valid UserDTO userDTO) {
userService.register(userDTO);
return Result.success();
}
@PutMapping("/password")
@RequiresAuthentication
public Result<?> updatePassword(@RequestBody PasswordUpdateDTO dto) {
userService.updatePassword(SecurityUtils.getCurrentUserId(),
dto.getOldPassword(), dto.getNewPassword());
return Result.success();
}
}
前端实现
API封装
// src/api/user.ts
import request from '@/utils/request'
export function login(data: LoginData) {
return request({
url: '/api/user/login',
method: 'post',
data
})
}
export function register(data: UserData) {
return request({
url: '/api/user/register',
method: 'post',
data
})
}
export function updatePassword(data: PasswordData) {
return request({
url: '/api/user/password',
method: 'put',
data
})
}
登录组件
<!-- src/views/login/index.vue -->
<template>
<div class="login-container">
<el-form
ref="loginForm"
:model="loginForm"
:rules="loginRules"
class="login-form"
>
<h3 class="title">学生考勤系统登录</h3>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
placeholder="用户名"
type="text"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
placeholder="密码"
type="password"
/>
</el-form-item>
<el-form-item>
<el-button
:loading="loading"
type="primary"
style="width:100%"
@click.prevent="handleLogin"
>
登录
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import type { FormInstance } from 'element-plus'
const router = useRouter()
const userStore = useUserStore()
const loginForm = reactive({
username: '',
password: ''
})
const loginRules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
}
const loading = ref(false)
const loginFormRef = ref<FormInstance>()
const handleLogin = async () => {
if (!loginFormRef.value) return
await loginFormRef.value.validate()
try {
loading.value = true
await userStore.login(loginForm)
router.push('/')
} catch (error) {
console.error('登录失败:', error)
} finally {
loading.value = false
}
}
</script>
Vuex状态管理
// src/stores/user.ts
import { defineStore } from 'pinia'
import { login, getUserInfo } from '@/api/user'
import { setToken, removeToken } from '@/utils/auth'
export const useUserStore = defineStore('user', {
state: () => ({
token: '',
userInfo: null,
permissions: []
}),
actions: {
async login(loginForm: LoginForm) {
try {
const { data } = await login(loginForm)
this.token = data.token
setToken(data.token)
} catch (error) {
removeToken()
throw error
}
},
async getUserInfo() {
try {
const { data } = await getUserInfo()
this.userInfo = data
this.permissions = data.permissions
return data
} catch (error) {
removeToken()
throw error
}
},
logout() {
this.token = ''
this.userInfo = null
this.permissions = []
removeToken()
}
}
})
路由守卫
// src/router/permission.ts
import router from './index'
import { useUserStore } from '@/stores/user'
import NProgress from 'nprogress'
router.beforeEach(async (to, from, next) => {
NProgress.start()
const userStore = useUserStore()
const hasToken = userStore.token
if (hasToken) {
if (to.path === '/login') {
next({ path: '/' })
} else {
const hasUserInfo = userStore.userInfo
if (hasUserInfo) {
next()
} else {
try {
await userStore.getUserInfo()
next()
} catch (error) {
userStore.logout()
next(`/login?redirect=${to.path}`)
}
}
}
} else {
if (to.meta.requiresAuth === false) {
next()
} else {
next(`/login?redirect=${to.path}`)
}
}
})
router.afterEach(() => {
NProgress.done()
})
以上就是用户管理模块的主要实现代码。这个模块实现了:
- 基于RBAC的权限控制
- 用户认证和授权
- JWT token的生成和验证
- 密码加密存储
- 前端路由权限控制
- 用户状态管理
模块的特点是:
- 安全性高:使用Spring Security + JWT
- 可扩展性好:基于RBAC模型
- 维护性强:模块化设计
- 用户体验好:前端交互流畅
二、考勤管理模块的实现
数据库设计
考勤记录表(attendance_record)
CREATE TABLE attendance_record (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '考勤记录ID',
class_id BIGINT NOT NULL COMMENT '班级ID',
course_id BIGINT NOT NULL COMMENT '课程ID',
teacher_id BIGINT NOT NULL COMMENT '教师ID',
start_time DATETIME NOT NULL COMMENT '开始时间',
end_time DATETIME NOT NULL COMMENT '结束时间',
status TINYINT DEFAULT 1 COMMENT '状态(0:未开始,1:进行中,2:已结束)',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (class_id) REFERENCES sys_class(id),
FOREIGN KEY (course_id) REFERENCES sys_course(id),
FOREIGN KEY (teacher_id) REFERENCES sys_user(id),
INDEX idx_class_course(class_id, course_id)
) COMMENT '考勤记录表';
考勤明细表(attendance_detail)
CREATE TABLE attendance_detail (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '考勤明细ID',
record_id BIGINT NOT NULL COMMENT '考勤记录ID',
student_id BIGINT NOT NULL COMMENT '学生ID',
status TINYINT NOT NULL COMMENT '考勤状态(0:未签到,1:正常,2:迟到,3:早退,4:旷课)',
sign_time DATETIME COMMENT '签到时间',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (record_id) REFERENCES attendance_record(id),
FOREIGN KEY (student_id) REFERENCES sys_user(id),
INDEX idx_record_student(record_id, student_id)
) COMMENT '考勤明细表';
后端实现
实体类
@Data
@TableName("attendance_record")
public class AttendanceRecord {
@TableId(type = IdType.AUTO)
private Long id;
private Long classId;
private Long courseId;
private Long teacherId;
private LocalDateTime startTime;
private LocalDateTime endTime;
private Integer status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
@Data
@TableName("attendance_detail")
public class AttendanceDetail {
@TableId(type = IdType.AUTO)
private Long id;
private Long recordId;
private Long studentId;
private Integer status;
private LocalDateTime signTime;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
DTO和VO类
@Data
public class AttendanceCreateDTO {
@NotNull(message = "班级ID不能为空")
private Long classId;
@NotNull(message = "课程ID不能为空")
private Long courseId;
@NotNull(message = "开始时间不能为空")
private LocalDateTime startTime;
@NotNull(message = "结束时间不能为空")
private LocalDateTime endTime;
}
@Data
public class AttendanceStatisticsVO {
private Long studentId;
private String studentName;
private Integer normalCount;
private Integer lateCount;
private Integer earlyLeaveCount;
private Integer absentCount;
}
服务接口
public interface AttendanceService {
// 教师发起签到
Long createAttendance(AttendanceCreateDTO dto);
// 学生签到
void signIn(Long recordId, Long studentId);
// 统计考勤
List<AttendanceStatisticsVO> getStatistics(Long classId, LocalDateTime startDate,
LocalDateTime endDate);
// 获取异常考勤记录
List<AttendanceDetail> getAbnormalRecords(Long classId, LocalDateTime date);
}
服务实现
@Service
@Transactional(rollbackFor = Exception.class)
public class AttendanceServiceImpl implements AttendanceService {
@Autowired
private AttendanceRecordMapper recordMapper;
@Autowired
private AttendanceDetailMapper detailMapper;
@Override
public Long createAttendance(AttendanceCreateDTO dto) {
// 验证课程和班级信息
validateCourseAndClass(dto.getCourseId(), dto.getClassId());
// 创建考勤记录
AttendanceRecord record = new AttendanceRecord();
BeanUtils.copyProperties(dto, record);
record.setTeacherId(SecurityUtils.getCurrentUserId());
record.setStatus(1);
recordMapper.insert(record);
// 初始化学生考勤明细
List<User> students = getClassStudents(dto.getClassId());
List<AttendanceDetail> details = students.stream()
.map(student -> createInitialDetail(record.getId(), student.getId()))
.collect(Collectors.toList());
detailMapper.insertBatch(details);
return record.getId();
}
@Override
public void signIn(Long recordId, Long studentId) {
AttendanceRecord record = recordMapper.selectById(recordId);
if (record == null || record.getStatus() != 1) {
throw new BusinessException("考勤未开始或已结束");
}
// 判断考勤状态
LocalDateTime now = LocalDateTime.now();
int status = calculateAttendanceStatus(now, record.getStartTime(),
record.getEndTime());
// 更新考勤明细
AttendanceDetail detail = detailMapper.selectOne(new QueryWrapper<AttendanceDetail>()
.eq("record_id", recordId)
.eq("student_id", studentId));
detail.setStatus(status);
detail.setSignTime(now);
detailMapper.updateById(detail);
}
@Override
public List<AttendanceStatisticsVO> getStatistics(Long classId,
LocalDateTime startDate, LocalDateTime endDate) {
return detailMapper.selectStatistics(classId, startDate, endDate);
}
private int calculateAttendanceStatus(LocalDateTime signTime,
LocalDateTime startTime, LocalDateTime endTime) {
if (signTime.isBefore(startTime.plusMinutes(10))) {
return 1; // 正常
} else if (signTime.isBefore(endTime)) {
return 2; // 迟到
} else {
return 4; // 旷课
}
}
}
控制器
@RestController
@RequestMapping("/api/attendance")
public class AttendanceController {
@Autowired
private AttendanceService attendanceService;
@PostMapping("/create")
@RequiresRoles("TEACHER")
public Result<Long> createAttendance(@RequestBody @Valid AttendanceCreateDTO dto) {
return Result.success(attendanceService.createAttendance(dto));
}
@PostMapping("/sign/{recordId}")
@RequiresRoles("STUDENT")
public Result<?> signIn(@PathVariable Long recordId) {
attendanceService.signIn(recordId, SecurityUtils.getCurrentUserId());
return Result.success();
}
@GetMapping("/statistics")
public Result<List<AttendanceStatisticsVO>> getStatistics(
@RequestParam Long classId,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate
) {
return Result.success(attendanceService.getStatistics(classId,
startDate.atStartOfDay(), endDate.atTime(23, 59, 59)));
}
}
前端实现
API封装
// src/api/attendance.ts
import request from '@/utils/request'
export function createAttendance(data: AttendanceCreateData) {
return request({
url: '/api/attendance/create',
method: 'post',
data
})
}
export function signIn(recordId: number) {
return request({
url: `/api/attendance/sign/${recordId}`,
method: 'post'
})
}
export function getStatistics(params: StatisticsQuery) {
return request({
url: '/api/attendance/statistics',
method: 'get',
params
})
}
教师发起签到组件
<!-- src/views/attendance/create.vue -->
<template>
<div class="attendance-create">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
>
<el-form-item label="班级" prop="classId">
<el-select v-model="form.classId" placeholder="请选择班级">
<el-option
v-for="item in classList"
:key="item.id"
:label="item.className"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="课程" prop="courseId">
<el-select v-model="form.courseId" placeholder="请选择课程">
<el-option
v-for="item in courseList"
:key="item.id"
:label="item.courseName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="考勤时间" required>
<el-col :span="11">
<el-form-item prop="startTime">
<el-date-picker
v-model="form.startTime"
type="datetime"
placeholder="开始时间"
/>
</el-form-item>
</el-col>
<el-col :span="2" class="text-center">-</el-col>
<el-col :span="11">
<el-form-item prop="endTime">
<el-date-picker
v-model="form.endTime"
type="datetime"
placeholder="结束时间"
/>
</el-form-item>
</el-col>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit">发起签到</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue'
import { createAttendance } from '@/api/attendance'
import { getClassList } from '@/api/class'
import { getCourseList } from '@/api/course'
import type { FormInstance } from 'element-plus'
const formRef = ref<FormInstance>()
const form = reactive({
classId: '',
courseId: '',
startTime: '',
endTime: ''
})
const classList = ref([])
const courseList = ref([])
onMounted(async () => {
classList.value = await getClassList()
courseList.value = await getCourseList()
})
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate()
await createAttendance(form)
ElMessage.success('考勤发起成功')
}
</script>
考勤统计组件
<!-- src/views/attendance/statistics.vue -->
<template>
<div class="attendance-statistics">
<div class="search-bar">
<el-form :inline="true" :model="queryForm">
<el-form-item label="班级">
<el-select v-model="queryForm.classId" placeholder="请选择班级">
<el-option
v-for="item in classList"
:key="item.id"
:label="item.className"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker
v-model="queryForm.dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="statisticsList" border>
<el-table-column prop="studentName" label="学生姓名" />
<el-table-column prop="normalCount" label="正常出勤" />
<el-table-column prop="lateCount" label="迟到" />
<el-table-column prop="earlyLeaveCount" label="早退" />
<el-table-column prop="absentCount" label="旷课" />
<el-table-column label="出勤率">
<template #default="{ row }">
{{ calculateAttendanceRate(row) }}%
</template>
</el-table-column>
</el-table>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { getStatistics } from '@/api/attendance'
const queryForm = reactive({
classId: '',
dateRange: []
})
const statisticsList = ref([])
const handleQuery = async () => {
const [startDate, endDate] = queryForm.dateRange
const params = {
classId: queryForm.classId,
startDate: startDate?.format('YYYY-MM-DD'),
endDate: endDate?.format('YYYY-MM-DD')
}
statisticsList.value = await getStatistics(params)
}
const calculateAttendanceRate = (row) => {
const total = row.normalCount + row.lateCount +
row.earlyLeaveCount + row.absentCount
return total === 0 ? 0 :
((row.normalCount / total) * 100).toFixed(2)
}
</script>
这个考勤管理模块实现了以下功能:
- 教师发起签到功能
- 学生在线签到
- 自动判断考勤状态(正常、迟到、早退、旷课)
- 考勤统计分析
- 异常考勤记录查询
系统特点:
- 实时性:支持实时签到和状态更新
- 准确性:自动判断考勤状态
- 可追溯:详细记录签到时间和状态
- 数据分析:支持多维度统计
- 权限控制:基于角色的功能访问控制
三、请假管理模块的实现
数据库设计
请假申请表(leave_application)
CREATE TABLE leave_application (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '请假ID',
student_id BIGINT NOT NULL COMMENT '学生ID',
class_id BIGINT NOT NULL COMMENT '班级ID',
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 '请假原因',
attachment_url VARCHAR(500) COMMENT '附件URL',
status TINYINT DEFAULT 0 COMMENT '状态(0:待审批,1:已批准,2:已驳回,3:已取消)',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (student_id) REFERENCES sys_user(id),
FOREIGN KEY (class_id) REFERENCES sys_class(id),
INDEX idx_student(student_id),
INDEX idx_class(class_id)
) COMMENT '请假申请表';
请假审批记录表(leave_approval)
CREATE TABLE leave_approval (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '审批ID',
leave_id BIGINT NOT NULL COMMENT '请假ID',
approver_id BIGINT NOT NULL COMMENT '审批人ID',
approver_role TINYINT NOT NULL COMMENT '审批人角色(1:教师,2:管理员)',
status TINYINT NOT NULL COMMENT '审批状态(1:同意,2:驳回)',
comment VARCHAR(200) COMMENT '审批意见',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (leave_id) REFERENCES leave_application(id),
FOREIGN KEY (approver_id) REFERENCES sys_user(id),
INDEX idx_leave(leave_id)
) COMMENT '请假审批记录表';
后端实现
实体类
@Data
@TableName("leave_application")
public class LeaveApplication {
@TableId(type = IdType.AUTO)
private Long id;
private Long studentId;
private Long classId;
private Integer type;
private LocalDateTime startTime;
private LocalDateTime endTime;
private String reason;
private String attachmentUrl;
private Integer status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
@Data
@TableName("leave_approval")
public class LeaveApproval {
@TableId(type = IdType.AUTO)
private Long id;
private Long leaveId;
private Long approverId;
private Integer approverRole;
private Integer status;
private String comment;
private LocalDateTime createTime;
}
DTO类
@Data
public class LeaveApplicationDTO {
@NotNull(message = "请假类型不能为空")
private Integer type;
@NotNull(message = "开始时间不能为空")
private LocalDateTime startTime;
@NotNull(message = "结束时间不能为空")
private LocalDateTime endTime;
@NotBlank(message = "请假原因不能为空")
private String reason;
private String attachmentUrl;
}
@Data
public class LeaveApprovalDTO {
@NotNull(message = "请假ID不能为空")
private Long leaveId;
@NotNull(message = "审批状态不能为空")
private Integer status;
private String comment;
}
服务接口
public interface LeaveService {
// 提交请假申请
Long submitLeave(LeaveApplicationDTO dto);
// 审批请假申请
void approveLeave(LeaveApprovalDTO dto);
// 取消请假申请
void cancelLeave(Long leaveId);
// 获取请假记录
IPage<LeaveVO> getLeaveRecords(LeaveQueryDTO query);
}
服务实现
@Service
@Transactional(rollbackFor = Exception.class)
public class LeaveServiceImpl implements LeaveService {
@Autowired
private LeaveApplicationMapper leaveMapper;
@Autowired
private LeaveApprovalMapper approvalMapper;
@Override
public Long submitLeave(LeaveApplicationDTO dto) {
// 获取当前学生信息
User student = SecurityUtils.getCurrentUser();
// 校验请假时间
validateLeaveTime(dto.getStartTime(), dto.getEndTime());
// 创建请假申请
LeaveApplication leave = new LeaveApplication();
BeanUtils.copyProperties(dto, leave);
leave.setStudentId(student.getId());
leave.setClassId(student.getClassId());
leave.setStatus(0);
leaveMapper.insert(leave);
return leave.getId();
}
@Override
public void approveLeave(LeaveApprovalDTO dto) {
// 获取请假申请
LeaveApplication leave = leaveMapper.selectById(dto.getLeaveId());
if (leave == null) {
throw new BusinessException("请假申请不存在");
}
// 校验状态
if (leave.getStatus() != 0) {
throw new BusinessException("请假申请已处理");
}
// 创建审批记录
User approver = SecurityUtils.getCurrentUser();
LeaveApproval approval = new LeaveApproval();
approval.setLeaveId(dto.getLeaveId());
approval.setApproverId(approver.getId());
approval.setApproverRole(approver.getRoleId());
approval.setStatus(dto.getStatus());
approval.setComment(dto.getComment());
approvalMapper.insert(approval);
// 更新请假状态
leave.setStatus(dto.getStatus());
leaveMapper.updateById(leave);
}
@Override
public void cancelLeave(Long leaveId) {
LeaveApplication leave = leaveMapper.selectById(leaveId);
if (leave == null) {
throw new BusinessException("请假申请不存在");
}
// 只能取消待审批的申请
if (leave.getStatus() != 0) {
throw new BusinessException("请假申请已处理,无法取消");
}
// 只能取消自己的申请
if (!leave.getStudentId().equals(SecurityUtils.getCurrentUserId())) {
throw new BusinessException("无权操作");
}
leave.setStatus(3);
leaveMapper.updateById(leave);
}
}
控制器
@RestController
@RequestMapping("/api/leave")
public class LeaveController {
@Autowired
private LeaveService leaveService;
@PostMapping("/submit")
@RequiresRoles("STUDENT")
public Result<Long> submitLeave(@RequestBody @Valid LeaveApplicationDTO dto) {
return Result.success(leaveService.submitLeave(dto));
}
@PostMapping("/approve")
@RequiresRoles({"TEACHER", "ADMIN"})
public Result<?> approveLeave(@RequestBody @Valid LeaveApprovalDTO dto) {
leaveService.approveLeave(dto);
return Result.success();
}
@PostMapping("/cancel/{leaveId}")
@RequiresRoles("STUDENT")
public Result<?> cancelLeave(@PathVariable Long leaveId) {
leaveService.cancelLeave(leaveId);
return Result.success();
}
@GetMapping("/list")
public Result<IPage<LeaveVO>> getLeaveRecords(LeaveQueryDTO query) {
return Result.success(leaveService.getLeaveRecords(query));
}
}
前端实现
API封装
// src/api/leave.ts
import request from '@/utils/request'
export function submitLeave(data: LeaveSubmitData) {
return request({
url: '/api/leave/submit',
method: 'post',
data
})
}
export function approveLeave(data: LeaveApprovalData) {
return request({
url: '/api/leave/approve',
method: 'post',
data
})
}
export function getLeaveRecords(params: LeaveQueryParams) {
return request({
url: '/api/leave/list',
method: 'get',
params
})
}
请假申请表单组件
<!-- src/views/leave/submit.vue -->
<template>
<div class="leave-submit">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
>
<el-form-item label="请假类型" prop="type">
<el-select v-model="form.type">
<el-option label="事假" :value="1" />
<el-option label="病假" :value="2" />
<el-option label="其他" :value="3" />
</el-select>
</el-form-item>
<el-form-item label="请假时间" required>
<el-col :span="11">
<el-form-item prop="startTime">
<el-date-picker
v-model="form.startTime"
type="datetime"
placeholder="开始时间"
/>
</el-form-item>
</el-col>
<el-col :span="2" class="text-center">-</el-col>
<el-col :span="11">
<el-form-item prop="endTime">
<el-date-picker
v-model="form.endTime"
type="datetime"
placeholder="结束时间"
/>
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="请假原因" prop="reason">
<el-input
v-model="form.reason"
type="textarea"
:rows="4"
placeholder="请输入请假原因"
/>
</el-form-item>
<el-form-item label="附件">
<el-upload
action="/api/file/upload"
:on-success="handleUploadSuccess"
>
<el-button type="primary">上传附件</el-button>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit">提交申请</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { submitLeave } from '@/api/leave'
import type { FormInstance } from 'element-plus'
const formRef = ref<FormInstance>()
const form = reactive({
type: undefined,
startTime: '',
endTime: '',
reason: '',
attachmentUrl: ''
})
const handleUploadSuccess = (response) => {
form.attachmentUrl = response.data
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate()
await submitLeave(form)
ElMessage.success('请假申请提交成功')
}
</script>
请假审批组件
<!-- src/views/leave/approval.vue -->
<template>
<div class="leave-approval">
<el-table :data="leaveList" border>
<el-table-column prop="studentName" label="学生姓名" />
<el-table-column prop="className" label="班级" />
<el-table-column prop="type" label="请假类型">
<template #default="{ row }">
{{ getLeaveType(row.type) }}
</template>
</el-table-column>
<el-table-column prop="startTime" label="开始时间" />
<el-table-column prop="endTime" label="结束时间" />
<el-table-column prop="reason" label="请假原因" show-overflow-tooltip />
<el-table-column label="操作">
<template #default="{ row }">
<el-button
v-if="row.status === 0"
type="primary"
size="small"
@click="handleApprove(row)"
>
审批
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 审批对话框 -->
<el-dialog
v-model="dialogVisible"
title="请假审批"
width="500px"
>
<el-form ref="approvalFormRef" :model="approvalForm" :rules="rules">
<el-form-item label="审批结果" prop="status">
<el-radio-group v-model="approvalForm.status">
<el-radio :label="1">同意</el-radio>
<el-radio :label="2">驳回</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="审批意见" prop="comment">
<el-input
v-model="approvalForm.comment"
type="textarea"
:rows="3"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitApproval">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue'
import { getLeaveRecords, approveLeave } from '@/api/leave'
const leaveList = ref([])
const dialogVisible = ref(false)
const currentLeave = ref(null)
const approvalForm = reactive({
status: null,
comment: ''
})
onMounted(() => {
loadLeaveRecords()
})
const loadLeaveRecords = async () => {
const res = await getLeaveRecords({ status: 0 })
leaveList.value = res.data
}
const handleApprove = (row) => {
currentLeave.value = row
dialogVisible.value = true
}
const submitApproval = async () => {
await approveLeave({
leaveId: currentLeave.value.id,
...approvalForm
})
ElMessage.success('审批成功')
dialogVisible.value = false
loadLeaveRecords()
}
</script>
请假历史记录查看组件
<!-- src/views/leave/history.vue -->
<template>
<div class="leave-history">
<!-- 搜索条件 -->
<div class="search-bar">
<el-form :inline="true" :model="queryForm">
<el-form-item label="请假类型">
<el-select v-model="queryForm.type" clearable>
<el-option label="事假" :value="1" />
<el-option label="病假" :value="2" />
<el-option label="其他" :value="3" />
</el-select>
</el-form-item>
<el-form-item label="请假状态">
<el-select v-model="queryForm.status" clearable>
<el-option label="待审批" :value="0" />
<el-option label="已批准" :value="1" />
<el-option label="已驳回" :value="2" />
<el-option label="已取消" :value="3" />
</el-select>
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker
v-model="queryForm.dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 请假记录表格 -->
<el-table :data="leaveList" border>
<el-table-column prop="createTime" label="申请时间" width="180" />
<el-table-column prop="type" label="请假类型" width="100">
<template #default="{ row }">
{{ getLeaveTypeText(row.type) }}
</template>
</el-table-column>
<el-table-column prop="startTime" label="开始时间" width="180" />
<el-table-column prop="endTime" label="结束时间" width="180" />
<el-table-column prop="reason" label="请假原因" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)">
{{ getStatusText(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button
v-if="row.status === 0"
type="primary"
link
@click="handleCancel(row.id)"
>
取消申请
</el-button>
<el-button
type="primary"
link
@click="handleDetail(row)"
>
查看详情
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页器 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="queryForm.pageNum"
v-model:page-size="queryForm.pageSize"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<!-- 详情对话框 -->
<el-dialog
v-model="dialogVisible"
title="请假详情"
width="600px"
>
<div class="leave-detail">
<div class="detail-item">
<span class="label">申请时间:</span>
<span>{{ currentLeave?.createTime }}</span>
</div>
<div class="detail-item">
<span class="label">请假类型:</span>
<span>{{ getLeaveTypeText(currentLeave?.type) }}</span>
</div>
<div class="detail-item">
<span class="label">请假时间:</span>
<span>{{ currentLeave?.startTime }} 至 {{ currentLeave?.endTime }}</span>
</div>
<div class="detail-item">
<span class="label">请假原因:</span>
<span>{{ currentLeave?.reason }}</span>
</div>
<div class="detail-item">
<span class="label">附件:</span>
<el-link
v-if="currentLeave?.attachmentUrl"
:href="currentLeave.attachmentUrl"
target="_blank"
>
查看附件
</el-link>
<span v-else>无</span>
</div>
<div class="detail-item">
<span class="label">审批状态:</span>
<el-tag :type="getStatusType(currentLeave?.status)">
{{ getStatusText(currentLeave?.status) }}
</el-tag>
</div>
<div class="approval-history" v-if="currentLeave?.approvals?.length">
<div class="title">审批记录</div>
<div
v-for="item in currentLeave.approvals"
:key="item.id"
class="approval-item"
>
<div class="approver">
{{ item.approverName }}
<span class="role">({{ getApproverRole(item.approverRole) }})</span>
</div>
<div class="result">
{{ item.status === 1 ? '同意' : '驳回' }}
<span class="time">{{ item.createTime }}</span>
</div>
<div class="comment" v-if="item.comment">
审批意见:{{ item.comment }}
</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { getLeaveRecords, cancelLeave } from '@/api/leave'
import { ElMessage, ElMessageBox } from 'element-plus'
const queryForm = reactive({
type: undefined,
status: undefined,
dateRange: [],
pageNum: 1,
pageSize: 10
})
const leaveList = ref([])
const total = ref(0)
const dialogVisible = ref(false)
const currentLeave = ref(null)
// 查询请假记录
const handleQuery = async () => {
const [startDate, endDate] = queryForm.dateRange || []
const params = {
...queryForm,
startDate: startDate?.format('YYYY-MM-DD'),
endDate: endDate?.format('YYYY-MM-DD')
}
const { data } = await getLeaveRecords(params)
leaveList.value = data.records
total.value = data.total
}
// 取消请假申请
const handleCancel = async (id: number) => {
try {
await ElMessageBox.confirm('确定要取消该请假申请吗?')
await cancelLeave(id)
ElMessage.success('取消成功')
handleQuery()
} catch (error) {
// 用户取消操作
}
}
// 查看详情
const handleDetail = (row) => {
currentLeave.value = row
dialogVisible.value = true
}
// 状态标签样式
const getStatusType = (status) => {
const statusMap = {
0: 'warning',
1: 'success',
2: 'danger',
3: 'info'
}
return statusMap[status]
}
// 工具函数
const getLeaveTypeText = (type) => {
const typeMap = {
1: '事假',
2: '病假',
3: '其他'
}
return typeMap[type]
}
const getStatusText = (status) => {
const statusMap = {
0: '待审批',
1: '已批准',
2: '已驳回',
3: '已取消'
}
return statusMap[status]
}
const getApproverRole = (role) => {
return role === 1 ? '教师' : '管理员'
}
</script>
<style lang="scss" scoped>
.leave-history {
.search-bar {
margin-bottom: 20px;
}
.pagination-container {
margin-top: 20px;
text-align: right;
}
.leave-detail {
.detail-item {
margin-bottom: 15px;
.label {
display: inline-block;
width: 100px;
color: #606266;
}
}
.approval-history {
margin-top: 20px;
border-top: 1px solid #EBEEF5;
padding-top: 20px;
.title {
font-weight: bold;
margin-bottom: 15px;
}
.approval-item {
margin-bottom: 15px;
padding: 10px;
background: #F5F7FA;
border-radius: 4px;
.approver {
.role {
color: #909399;
margin-left: 5px;
}
}
.result {
margin: 5px 0;
.time {
color: #909399;
margin-left: 10px;
font-size: 13px;
}
}
.comment {
color: #606266;
}
}
}
}
}
</style>
请假统计分析服务
@Service
public class LeaveStatisticsService {
@Autowired
private LeaveApplicationMapper leaveMapper;
// 获取请假统计数据
public LeaveStatisticsVO getLeaveStatistics(LeaveStatisticsQueryDTO query) {
LeaveStatisticsVO statistics = new LeaveStatisticsVO();
// 请假类型分布
statistics.setTypeDistribution(leaveMapper.selectTypeDistribution(query));
// 请假时长统计
statistics.setDurationStatistics(leaveMapper.selectDurationStatistics(query));
// 请假趋势分析
statistics.setTrendAnalysis(leaveMapper.selectTrendAnalysis(query));
return statistics;
}
// 导出请假记录
public void exportLeaveRecords(LeaveQueryDTO query, HttpServletResponse response) {
List<LeaveExportVO> records = leaveMapper.selectExportList(query);
ExcelWriter writer = ExcelUtil.getWriter();
writer.addHeaderAlias("studentName", "学生姓名");
writer.addHeaderAlias("className", "班级");
writer.addHeaderAlias("leaveType", "请假类型");
writer.addHeaderAlias("startTime", "开始时间");
writer.addHeaderAlias("endTime", "结束时间");
writer.addHeaderAlias("duration", "请假时长(天)");
writer.addHeaderAlias("reason", "请假原因");
writer.addHeaderAlias("status", "状态");
writer.write(records, true);
response.setContentType("application/vnd.ms-excel;charset=utf-8");
response.setHeader("Content-Disposition", "attachment;filename=leave_records.xls");
writer.flush(response.getOutputStream());
}
}
请假审批流程优化
- 添加审批流程配置表:
CREATE TABLE leave_approval_flow (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
class_id BIGINT COMMENT '班级ID',
min_days INT COMMENT '最小天数',
max_days INT COMMENT '最大天数',
approver_roles VARCHAR(100) COMMENT '审批角色列表',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME ON UPDATE CURRENT_TIMESTAMP
) COMMENT '请假审批流程配置表';
- 审批服务优化:
@Service
public class LeaveApprovalService {
@Autowired
private LeaveApprovalFlowMapper flowMapper;
// 获取审批流程
public List<Integer> getApprovalFlow(Long classId, int leaveDays) {
LeaveApprovalFlow flow = flowMapper.selectOne(new QueryWrapper<LeaveApprovalFlow>()
.eq("class_id", classId)
.le("min_days", leaveDays)
.ge("max_days", leaveDays));
if (flow == null) {
return Collections.singletonList(1); // 默认只需要教师审批
}
return Arrays.stream(flow.getApproverRoles().split(","))
.map(Integer::parseInt)
.collect(Collectors.toList());
}
// 处理审批
public void handleApproval(LeaveApprovalDTO dto) {
LeaveApplication leave = leaveMapper.selectById(dto.getLeaveId());
List<Integer> flowRoles = getApprovalFlow(leave.getClassId(),
calculateLeaveDays(leave));
// 创建审批记录
createApprovalRecord(dto);
// 判断是否需要继续审批
if (isLastApproval(dto.getLeaveId(), flowRoles)) {
// 更新请假状态为最终状态
updateLeaveStatus(dto.getLeaveId(), dto.getStatus());
// 发送通知
sendNotification(leave, dto.getStatus());
}
}
// 发送审批通知
private void sendNotification(LeaveApplication leave, Integer status) {
// 获取学生信息
User student = userMapper.selectById(leave.getStudentId());
// 发送站内通知
NotificationMessage message = new NotificationMessage();
message.setUserId(student.getId());
message.setTitle("请假审批通知");
message.setContent(String.format("您的请假申请已%s",
status == 1 ? "批准" : "驳回"));
notificationService.sendNotification(message);
// 发送邮件通知
if (StringUtils.isNotBlank(student.getEmail())) {
emailService.sendSimpleMail(student.getEmail(),
"请假审批通知",
message.getContent());
}
}
}
四、系统管理模块的实现
数据库设计
班级表(sys_class)
CREATE TABLE sys_class (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '班级ID',
class_name VARCHAR(50) NOT NULL COMMENT '班级名称',
grade VARCHAR(20) NOT NULL COMMENT '年级',
department VARCHAR(50) COMMENT '院系',
major VARCHAR(50) COMMENT '专业',
head_teacher_id BIGINT COMMENT '班主任ID',
student_count INT DEFAULT 0 COMMENT '学生人数',
status TINYINT DEFAULT 1 COMMENT '状态(0:禁用,1:正常)',
remark VARCHAR(200) COMMENT '备注',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (head_teacher_id) REFERENCES sys_user(id),
UNIQUE INDEX idx_class_name(class_name)
) COMMENT '班级表';
课程表(sys_course)
CREATE TABLE sys_course (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '课程ID',
course_name VARCHAR(100) NOT NULL COMMENT '课程名称',
course_code VARCHAR(50) NOT NULL COMMENT '课程代码',
credit DECIMAL(4,1) NOT NULL COMMENT '学分',
period INT NOT NULL COMMENT '课时',
type TINYINT COMMENT '课程类型(1:必修,2:选修)',
department VARCHAR(50) COMMENT '开课院系',
status TINYINT DEFAULT 1 COMMENT '状态(0:禁用,1:正常)',
description TEXT COMMENT '课程描述',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME ON UPDATE CURRENT_TIMESTAMP,
UNIQUE INDEX idx_course_code(course_code)
) COMMENT '课程表';
教师课程关联表(sys_teacher_course)
CREATE TABLE sys_teacher_course (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
teacher_id BIGINT NOT NULL COMMENT '教师ID',
course_id BIGINT NOT NULL COMMENT '课程ID',
semester VARCHAR(20) NOT NULL COMMENT '学期',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (teacher_id) REFERENCES sys_user(id),
FOREIGN KEY (course_id) REFERENCES sys_course(id),
UNIQUE INDEX idx_teacher_course(teacher_id, course_id, semester)
) COMMENT '教师课程关联表';
班级课程关联表(sys_class_course)
CREATE TABLE sys_class_course (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
class_id BIGINT NOT NULL COMMENT '班级ID',
course_id BIGINT NOT NULL COMMENT '课程ID',
teacher_id BIGINT NOT NULL COMMENT '任课教师ID',
semester VARCHAR(20) NOT NULL COMMENT '学期',
schedule VARCHAR(200) COMMENT '上课时间',
classroom VARCHAR(50) COMMENT '教室',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (class_id) REFERENCES sys_class(id),
FOREIGN KEY (course_id) REFERENCES sys_course(id),
FOREIGN KEY (teacher_id) REFERENCES sys_user(id),
UNIQUE INDEX idx_class_course(class_id, course_id, semester)
) COMMENT '班级课程关联表';
后端实现
实体类
@Data
@TableName("sys_class")
public class Class {
@TableId(type = IdType.AUTO)
private Long id;
private String className;
private String grade;
private String department;
private String major;
private Long headTeacherId;
private Integer studentCount;
private Integer status;
private String remark;
private LocalDateTime createTime;
private LocalDateTime updateTime;
@TableField(exist = false)
private String headTeacherName;
}
@Data
@TableName("sys_course")
public class Course {
@TableId(type = IdType.AUTO)
private Long id;
private String courseName;
private String courseCode;
private BigDecimal credit;
private Integer period;
private Integer type;
private String department;
private Integer status;
private String description;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
Service层实现
@Service
@Transactional(rollbackFor = Exception.class)
public class ClassServiceImpl extends ServiceImpl<ClassMapper, Class> implements ClassService {
@Autowired
private UserMapper userMapper;
@Override
public IPage<ClassVO> listClasses(ClassQueryDTO query) {
return baseMapper.selectClassList(new Page<>(query.getPageNum(), query.getPageSize()), query);
}
@Override
public void addClass(ClassDTO dto) {
// 验证班级名称唯一性
validateClassName(dto.getClassName());
Class entity = new Class();
BeanUtils.copyProperties(dto, entity);
baseMapper.insert(entity);
}
@Override
public void updateClass(Long id, ClassDTO dto) {
Class entity = baseMapper.selectById(id);
if (entity == null) {
throw new BusinessException("班级不存在");
}
if (!entity.getClassName().equals(dto.getClassName())) {
validateClassName(dto.getClassName());
}
BeanUtils.copyProperties(dto, entity);
baseMapper.updateById(entity);
}
@Override
public void deleteClass(Long id) {
// 检查是否有关联学生
Long studentCount = userMapper.selectCount(new QueryWrapper<User>()
.eq("class_id", id));
if (studentCount > 0) {
throw new BusinessException("班级下存在学生,无法删除");
}
baseMapper.deleteById(id);
}
}
@Service
@Transactional(rollbackFor = Exception.class)
public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course> implements CourseService {
@Override
public IPage<CourseVO> listCourses(CourseQueryDTO query) {
return baseMapper.selectCourseList(new Page<>(query.getPageNum(), query.getPageSize()), query);
}
@Override
public void addCourse(CourseDTO dto) {
// 验证课程代码唯一性
validateCourseCode(dto.getCourseCode());
Course entity = new Course();
BeanUtils.copyProperties(dto, entity);
baseMapper.insert(entity);
}
@Override
public void assignTeacher(CourseTeacherDTO dto) {
// 验证教师和课程是否存在
validateTeacherAndCourse(dto.getTeacherId(), dto.getCourseId());
TeacherCourse relation = new TeacherCourse();
BeanUtils.copyProperties(dto, relation);
teacherCourseMapper.insert(relation);
}
}
Controller层实现
@RestController
@RequestMapping("/api/system/class")
public class ClassController {
@Autowired
private ClassService classService;
@GetMapping("/list")
public Result<IPage<ClassVO>> listClasses(ClassQueryDTO query) {
return Result.success(classService.listClasses(query));
}
@PostMapping
@RequiresPermissions("system:class:add")
public Result<?> addClass(@RequestBody @Valid ClassDTO dto) {
classService.addClass(dto);
return Result.success();
}
@PutMapping("/{id}")
@RequiresPermissions("system:class:edit")
public Result<?> updateClass(@PathVariable Long id, @RequestBody @Valid ClassDTO dto) {
classService.updateClass(id, dto);
return Result.success();
}
@DeleteMapping("/{id}")
@RequiresPermissions("system:class:delete")
public Result<?> deleteClass(@PathVariable Long id) {
classService.deleteClass(id);
return Result.success();
}
}
@RestController
@RequestMapping("/api/system/course")
public class CourseController {
@Autowired
private CourseService courseService;
@GetMapping("/list")
public Result<IPage<CourseVO>> listCourses(CourseQueryDTO query) {
return Result.success(courseService.listCourses(query));
}
@PostMapping
@RequiresPermissions("system:course:add")
public Result<?> addCourse(@RequestBody @Valid CourseDTO dto) {
courseService.addCourse(dto);
return Result.success();
}
@PostMapping("/assign-teacher")
@RequiresPermissions("system:course:assign")
public Result<?> assignTeacher(@RequestBody @Valid CourseTeacherDTO dto) {
courseService.assignTeacher(dto);
return Result.success();
}
}
前端实现
班级管理组件
<!-- src/views/system/class/index.vue -->
<template>
<div class="class-manage">
<!-- 搜索工具栏 -->
<div class="search-bar">
<el-form :inline="true" :model="queryParams">
<el-form-item label="班级名称">
<el-input
v-model="queryParams.className"
placeholder="请输入班级名称"
clearable
/>
</el-form-item>
<el-form-item label="年级">
<el-select v-model="queryParams.grade" clearable>
<el-option
v-for="item in gradeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 操作按钮区 -->
<div class="toolbar">
<el-button
type="primary"
@click="handleAdd"
v-hasPermi="['system:class:add']"
>
新增班级
</el-button>
</div>
<!-- 班级列表 -->
<el-table :data="classList" border>
<el-table-column prop="className" label="班级名称" />
<el-table-column prop="grade" label="年级" />
<el-table-column prop="department" label="院系" />
<el-table-column prop="major" label="专业" />
<el-table-column prop="headTeacherName" label="班主任" />
<el-table-column prop="studentCount" label="学生人数" />
<el-table-column prop="status" label="状态">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
{{ row.status === 1 ? '正常' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button
type="primary"
link
@click="handleEdit(row)"
v-hasPermi="['system:class:edit']"
>
编辑
</el-button>
<el-button
type="primary"
link
@click="handleDelete(row)"
v-hasPermi="['system:class:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页器 -->
<el-pagination
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
<!-- 编辑对话框 -->
<el-dialog
:title="dialogTitle"
v-model="dialogVisible"
width="500px"
append-to-body
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
>
<el-form-item label="班级名称" prop="className">
<el-input v-model="form.className" placeholder="请输入班级名称" />
</el-form-item>
<el-form-item label="年级" prop="grade">
<el-select v-model="form.grade">
<el-option
v-for="item in gradeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="院系" prop="department">
<el-input v-model="form.department" placeholder="请输入院系" />
</el-form-item>
<el-form-item label="专业" prop="major">
<el-input v-model="form.major" placeholder="请输入专业" />
</el-form-item>
<el-form-item label="班主任" prop="headTeacherId">
<el-select
v-model="form.headTeacherId"
placeholder="请选择班主任"
filterable
>
<el-option
v-for="item in teacherOptions"
:key="item.id"
:label="item.realName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio :label="1">正常</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="form.remark"
type="textarea"
placeholder="请输入备注"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue'
import {
listClass,
addClass,
updateClass,
deleteClass
} from '@/api/system/class'
import { listTeachers } from '@/api/system/user'
import { ElMessage, ElMessageBox } from 'element-plus'
// 查询参数
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
className: '',
grade: undefined
})
// 班级列表数据
const classList = ref([])
const total = ref(0)
// 表单数据
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formRef = ref()
const form = reactive({
id: undefined,
className: '',
grade: '',
department: '',
major: '',
headTeacherId: undefined,
status: 1,
remark: ''
})
// 教师选项
const teacherOptions = ref([])
// 获取班级列表
const getList = async () => {
const { data } = await listClass(queryParams)
classList.value = data.records
total.value = data.total
}
// 获取教师列表
const getTeachers = async () => {
const { data } = await listTeachers({ roleCode: 'TEACHER' })
teacherOptions.value = data
}
// 新增班级
const handleAdd = () => {
resetForm()
dialogTitle.value = '新增班级'
dialogVisible.value = true
}
// 编辑班级
const handleEdit = (row) => {
Object.assign(form, row)
dialogTitle.value = '编辑班级'
dialogVisible.value = true
}
// 删除班级
const handleDelete = async (row) => {
try {
await ElMessageBox.confirm('确认要删除该班级吗?')
await deleteClass(row.id)
ElMessage.success('删除成功')
getList()
} catch (error) {
// 用户取消操作
}
}
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate()
if (form.id) {
await updateClass(form.id, form)
} else {
await addClass(form)
}
ElMessage.success('操作成功')
dialogVisible.value = false
getList()
}
// 重置表单
const resetForm = () => {
if (formRef.value) {
formRef.value.resetFields()
}
Object.assign(form, {
id: undefined,
className: '',
grade: '',
department: '',
major: '',
headTeacherId: undefined,
status: 1,
remark: ''
})
}
onMounted(() => {
getList()
getTeachers()
})
</script>
<style lang="scss" scoped>
.class-manage {
.search-bar {
margin-bottom: 20px;
}
.toolbar {
margin-bottom: 20px;
}
}
</style>
课程管理组件
<!-- src/views/system/course/index.vue -->
<template>
<div class="course-manage">
<!-- 搜索工具栏 -->
<div class="search-bar">
<el-form :inline="true" :model="queryParams">
<el-form-item label="课程名称">
<el-input
v-model="queryParams.courseName"
placeholder="请输入课程名称"
clearable
/>
</el-form-item>
<el-form-item label="课程代码">
<el-input
v-model="queryParams.courseCode"
placeholder="请输入课程代码"
clearable
/>
</el-form-item>
<el-form-item label="课程类型">
<el-select v-model="queryParams.type" clearable>
<el-option label="必修" :value="1" />
<el-option label="选修" :value="2" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 操作按钮区 -->
<div class="toolbar">
<el-button
type="primary"
@click="handleAdd"
v-hasPermi="['system:course:add']"
>
新增课程
</el-button>
<el-button
type="success"
@click="handleAssign"
v-hasPermi="['system:course:assign']"
>
课程分配
</el-button>
</div>
<!-- 课程列表 -->
<el-table :data="courseList" border>
<el-table-column prop="courseName" label="课程名称" />
<el-table-column prop="courseCode" label="课程代码" />
<el-table-column prop="credit" label="学分" />
<el-table-column prop="period" label="课时" />
<el-table-column prop="type" label="类型">
<template #default="{ row }">
{{ row.type === 1 ? '必修' : '选修' }}
</template>
</el-table-column>
<el-table-column prop="department" label="开课院系" />
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button
type="primary"
link
@click="handleEdit(row)"
>
编辑
</el-button>
<el-button
type="primary"
link
@click="handleDelete(row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页器 -->
<el-pagination
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
<!-- 课程表单对话框 -->
<el-dialog
:title="dialogTitle"
v-model="dialogVisible"
width="500px"
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
>
<el-form-item label="课程名称" prop="courseName">
<el-input v-model="form.courseName" />
</el-form-item>
<el-form-item label="课程代码" prop="courseCode">
<el-input v-model="form.courseCode" />
</el-form-item>
<el-form-item label="学分" prop="credit">
<el-input-number v-model="form.credit" :precision="1" :step="0.5" />
</el-form-item>
<el-form-item label="课时" prop="period">
<el-input-number v-model="form.period" :min="1" />
</el-form-item>
<el-form-item label="课程类型" prop="type">
<el-select v-model="form.type">
<el-option label="必修" :value="1" />
<el-option label="选修" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="开课院系" prop="department">
<el-input v-model="form.department" />
</el-form-item>
<el-form-item label="课程描述" prop="description">
<el-input
v-model="form.description"
type="textarea"
:rows="3"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
<!-- 课程分配对话框 -->
<el-dialog
title="课程分配"
v-model="assignDialogVisible"
width="600px"
>
<el-form
ref="assignFormRef"
:model="assignForm"
:rules="assignRules"
label-width="100px"
>
<el-form-item label="课程" prop="courseId">
<el-select
v-model="assignForm.courseId"
filterable
placeholder="请选择课程"
>
<el-option
v-for="item in courseList"
:key="item.id"
:label="item.courseName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="教师" prop="teacherId">
<el-select
v-model="assignForm.teacherId"
filterable
placeholder="请选择教师"
>
<el-option
v-for="item in teacherOptions"
:key="item.id"
:label="item.realName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="semester">
<el-select v-model="assignForm.semester">
<el-option
v-for="item in semesterOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="assignDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleAssignSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
// ... 组件逻辑实现与班级管理类似,此处省略
</script>
管理员功能
- 系统参数配置:
@Data
@TableName("sys_config")
public class SysConfig {
@TableId(type = IdType.AUTO)
private Long id;
private String configKey;
private String configValue;
private String remark;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
@Service
public class ConfigService {
@Autowired
private SysConfigMapper configMapper;
@Autowired
private RedisCache redisCache;
public String getConfigValue(String key) {
String cacheKey = getCacheKey(key);
String value = redisCache.getCacheObject(cacheKey);
if (StringUtils.isNotEmpty(value)) {
return value;
}
SysConfig config = configMapper.selectOne(new QueryWrapper<SysConfig>()
.eq("config_key", key));
if (config != null) {
redisCache.setCacheObject(cacheKey, config.getConfigValue());
return config.getConfigValue();
}
return null;
}
public void updateConfig(String key, String value) {
SysConfig config = configMapper.selectOne(new QueryWrapper<SysConfig>()
.eq("config_key", key));
if (config != null) {
config.setConfigValue(value);
configMapper.updateById(config);
} else {
config = new SysConfig();
config.setConfigKey(key);
config.setConfigValue(value);
configMapper.insert(config);
}
redisCache.deleteObject(getCacheKey(key));
}
}
- 数据备份功能:
@Service
public class BackupService {
@Value("${backup.path}")
private String backupPath;
public void backup() {
String fileName = String.format("backup_%s.sql",
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
String filePath = backupPath + File.separator + fileName;
// 执行数据库备份命令
String command = String.format(
"mysqldump -u%s -p%s %s > %s",
username, password, database, filePath);
Process process = Runtime.getRuntime().exec(command);
process.waitFor();
// 记录备份日志
BackupLog log = new BackupLog();
log.setFileName(fileName);
log.setFilePath(filePath);
log.setFileSize(new File(filePath).length());
backupLogMapper.insert(log);
}
public void restore(String fileName) {
String filePath = backupPath + File.separator + fileName;
// 执行数据库恢复命令
String command = String.format(
"mysql -u%s -p%s %s < %s",
username, password, database, filePath);
Process process = Runtime.getRuntime().exec(command);
process.waitFor();
// 记录恢复日志
RestoreLog log = new RestoreLog();
log.setFileName(fileName);
log.setFilePath(filePath);
restoreLogMapper.insert(log);
}
}
- 操作日志功能:
@Data
@TableName("sys_operation_log")
public class OperationLog {
@TableId(type = IdType.AUTO)
private Long id;
private String operationModule;
private String operationType;
private String operationDesc;
private String requestMethod;
private String requestParams;
private String requestIp;
private Long operationUser;
private LocalDateTime operationTime;
private Integer status;
private String errorMsg;
}
@Aspect
@Component
public class OperationLogAspect {
@Autowired
private OperationLogMapper logMapper;
@Around("@annotation(operationLog)")
public Object around(ProceedingJoinPoint point, OperationLog operationLog) {
OperationLog log = new OperationLog();
try {
// 记录请求信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
log.setRequestMethod(request.getMethod());
log.setRequestIp(IpUtils.getIpAddr(request));
log.setRequestParams(JsonUtils.toJsonString(point.getArgs()));
// 记录操作信息
log.setOperationModule(operationLog.operationModule());
log.setOperationType(operationLog.operationType());
log.setOperationDesc(operationLog.operationDesc());
log.setOperationUser(SecurityUtils.getCurrentUserId());
log.setOperationTime(LocalDateTime.now());
// 执行目标方法
Object result = point.proceed();
log.setStatus(1);
return result;
} catch (Throwable e) {
log.setStatus(0);
log.setErrorMsg(e.getMessage());
throw new RuntimeException(e);
} finally {
logMapper.insert(log);
}
}
}
// 操作日志注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
String operationModule() default ""; // 操作模块
String operationType() default ""; // 操作类型
String operationDesc() default ""; // 操作描述
}
系统监控功能
@Data
public class ServerInfo {
private CpuInfo cpu;
private MemInfo mem;
private JvmInfo jvm;
private DiskInfo disk;
@Data
public static class CpuInfo {
private int cpuNum;
private double total;
private double sys;
private double used;
private double wait;
private double free;
}
@Data
public static class MemInfo {
private double total;
private double used;
private double free;
private double usage;
}
// 其他内部类...
}
@Service
public class MonitorService {
public ServerInfo getServerInfo() {
ServerInfo info = new ServerInfo();
// 获取CPU信息
SystemInfo si = new SystemInfo();
HardwareAbstractionLayer hal = si.getHardware();
CentralProcessor processor = hal.getProcessor();
CpuInfo cpu = new CpuInfo();
cpu.setCpuNum(processor.getLogicalProcessorCount());
// 设置其他CPU信息...
info.setCpu(cpu);
// 获取内存信息
GlobalMemory memory = hal.getMemory();
MemInfo mem = new MemInfo();
mem.setTotal(memory.getTotal());
mem.setUsed(memory.getTotal() - memory.getAvailable());
mem.setFree(memory.getAvailable());
mem.setUsage(mem.getUsed() / mem.getTotal() * 100);
info.setMem(mem);
// 获取其他系统信息...
return info;
}
}
用户在线管理
@Data
public class UserOnline {
private String tokenId;
private Long userId;
private String username;
private String ipaddr;
private String loginTime;
private String browser;
private String os;
}
@Service
public class UserOnlineService {
@Autowired
private RedisCache redisCache;
public List<UserOnline> getOnlineUsers() {
Collection<String> keys = redisCache.keys(Constants.LOGIN_TOKEN_KEY + "*");
List<UserOnline> userOnlineList = new ArrayList<>();
for (String key : keys) {
LoginUser user = redisCache.getCacheObject(key);
if (user != null) {
UserOnline online = new UserOnline();
online.setTokenId(key);
online.setUserId(user.getUserId());
online.setUsername(user.getUsername());
online.setIpaddr(user.getIpaddr());
online.setLoginTime(DateUtils.formatDateTime(user.getLoginTime()));
online.setBrowser(user.getBrowser());
online.setOs(user.getOs());
userOnlineList.add(online);
}
}
return userOnlineList;
}
public void forceLogout(String tokenId) {
redisCache.deleteObject(tokenId);
}
}
定时任务管理
@Data
@TableName("sys_job")
public class SysJob {
@TableId(type = IdType.AUTO)
private Long jobId;
private String jobName;
private String jobGroup;
private String invokeTarget;
private String cronExpression;
private String misfirePolicy;
private String concurrent;
private String status;
private String remark;
}
@Service
public class JobService {
@Autowired
private Scheduler scheduler;
public void addJob(SysJob job) throws SchedulerException {
Class<? extends Job> jobClass = getQuartzJobClass(job);
JobDetail jobDetail = JobBuilder.newJob(jobClass)
.withIdentity(job.getJobName(), job.getJobGroup())
.build();
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(job.getJobName(), job.getJobGroup())
.withSchedule(cronScheduleBuilder)
.build();
scheduler.scheduleJob(jobDetail, trigger);
}
public void pauseJob(Long jobId) throws SchedulerException {
SysJob job = jobMapper.selectById(jobId);
scheduler.pauseJob(JobKey.jobKey(job.getJobName(), job.getJobGroup()));
}
public void resumeJob(Long jobId) throws SchedulerException {
SysJob job = jobMapper.selectById(jobId);
scheduler.resumeJob(JobKey.jobKey(job.getJobName(), job.getJobGroup()));
}
}
数据字典管理
@Data
@TableName("sys_dict_type")
public class DictType {
@TableId(type = IdType.AUTO)
private Long dictId;
private String dictName;
private String dictType;
private String status;
private String remark;
}
@Data
@TableName("sys_dict_data")
public class DictData {
@TableId(type = IdType.AUTO)
private Long dictCode;
private Long dictSort;
private String dictLabel;
private String dictValue;
private String dictType;
private String status;
private String remark;
}
@Service
public class DictService {
@Autowired
private DictTypeMapper dictTypeMapper;
@Autowired
private DictDataMapper dictDataMapper;
public List<DictData> getDictData(String dictType) {
return dictDataMapper.selectList(new QueryWrapper<DictData>()
.eq("dict_type", dictType)
.eq("status", "0")
.orderByAsc("dict_sort"));
}
@CacheEvict(value = "dict", key = "#dictType")
public void clearDictCache(String dictType) {
// 清除缓存
}
}
这些实现包括:
- 完整的操作日志记录功能
- 服务器监控功能
- 在线用户管理
- 定时任务管理
- 数据字典管理
主要特点:
- 模块化设计:各个功能模块独立
- 缓存支持:使用Redis缓存提升性能
- 异步处理:日志记录采用异步方式
- 安全性:操作记录和权限控制
- 可扩展性:便于添加新的管理功能
系统管理模块为整个应用提供了完整的后台管理支持,方便管理员进行系统维护和监控。