首次提交

yjc
hd 2 days ago
parent d2d52257a8
commit 300a50843c

58
.gitignore vendored

@ -0,0 +1,58 @@
# ============================
# Java / Maven
# ============================
target/
*.class
*.jar
*.war
*.log
*.logs
# ============================
# IDE
# ============================
.idea/
*.iml
.vscode/
.settings/
.project
.classpath
.factorypath
*.swp
*.swo
*~
# ============================
# OS
# ============================
.DS_Store
Thumbs.db
Desktop.ini
# ============================
# Logs
# ============================
logs/
*.log
*.log.*
# ============================
# Temp / Cache
# ============================
*.tmp
*.bak
*.cache
# ============================
# Environment / Sensitive
# ============================
.env
.env.local
application-local.yml
application-dev.yml
# ============================
# Upload / Data
# ============================
upload/
data/

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId>com.attendance</groupId>
<artifactId>attendance-system-server</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>attendance-system-server</name>
<description>教室智能人脸考勤系统后端服务</description>
<properties>
<java.version>1.8</java.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version>
<knife4j.version>4.4.0</knife4j.version>
<jwt.version>4.4.0</jwt.version>
<fastjson2.version>2.0.49</fastjson2.version>
<minio.version>8.5.7</minio.version>
</properties>
<dependencies>
<!-- Hutool工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- MySQL & MyBatis-Plus -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- Knife4j (Swagger) -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<!-- JSON -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<!-- MinIO -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,756 @@
/*
Navicat Premium Dump SQL
Source Server : JavaProjects
Source Server Type : MySQL
Source Server Version : 80040 (8.0.40)
Source Host : localhost:3306
Source Schema : attendance_system
Target Server Type : MySQL
Target Server Version : 80040 (8.0.40)
File Encoding : 65001
Date: 04/06/2026 11:38:13
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for att_detail
-- ----------------------------
DROP TABLE IF EXISTS `att_detail`;
CREATE TABLE `att_detail` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`task_id` bigint UNSIGNED NOT NULL COMMENT '考勤任务ID',
`record_id` bigint UNSIGNED NOT NULL COMMENT '考勤记录ID',
`student_id` bigint UNSIGNED NOT NULL COMMENT '学生ID',
`student_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '学号',
`student_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '学生姓名',
`course_id` bigint UNSIGNED NOT NULL COMMENT '课程ID',
`classroom_id` bigint UNSIGNED NOT NULL COMMENT '教室ID',
`att_date` date NOT NULL COMMENT '考勤日期',
`check_in_time` datetime NULL DEFAULT NULL COMMENT '签到时间',
`check_out_time` datetime NULL DEFAULT NULL COMMENT '签退时间',
`att_status` tinyint NOT NULL DEFAULT 0 COMMENT '考勤状态0-未签到 1-正常 2-迟到 3-缺勤 4-早退 5-请假',
`check_type` tinyint NOT NULL DEFAULT 1 COMMENT '签到方式1-人脸识别 2-手动补签',
`face_similarity` decimal(5, 2) NULL DEFAULT NULL COMMENT '人脸相似度',
`face_image` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '抓拍人脸图片URL',
`device_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '签到设备ID',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
`school_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属学校ID',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_task_student`(`task_id` ASC, `student_id` ASC) USING BTREE,
INDEX `idx_record_id`(`record_id` ASC) USING BTREE,
INDEX `idx_student_id`(`student_id` ASC) USING BTREE,
INDEX `idx_att_status`(`att_status` ASC) USING BTREE,
INDEX `idx_att_date`(`att_date` ASC) USING BTREE,
INDEX `idx_course_id`(`course_id` ASC) USING BTREE,
INDEX `idx_school_id`(`school_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '考勤明细表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of att_detail
-- ----------------------------
-- ----------------------------
-- Table structure for att_record
-- ----------------------------
DROP TABLE IF EXISTS `att_record`;
CREATE TABLE `att_record` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`task_id` bigint UNSIGNED NOT NULL COMMENT '考勤任务ID',
`course_id` bigint UNSIGNED NOT NULL COMMENT '课程ID',
`classroom_id` bigint UNSIGNED NOT NULL COMMENT '教室ID',
`teacher_id` bigint UNSIGNED NOT NULL COMMENT '教师ID',
`att_date` date NOT NULL COMMENT '考勤日期',
`start_time` datetime NOT NULL COMMENT '开始时间',
`end_time` datetime NOT NULL COMMENT '结束时间',
`total_count` int NOT NULL DEFAULT 0 COMMENT '应到人数',
`actual_count` int NOT NULL DEFAULT 0 COMMENT '实到人数',
`absent_count` int NOT NULL DEFAULT 0 COMMENT '缺勤人数',
`late_count` int NOT NULL DEFAULT 0 COMMENT '迟到人数',
`leave_early_count` int NOT NULL DEFAULT 0 COMMENT '早退人数',
`attendance_rate` decimal(5, 2) NULL DEFAULT NULL COMMENT '出勤率%',
`absent_rate` decimal(5, 2) NULL DEFAULT NULL COMMENT '缺勤率%',
`record_status` tinyint NOT NULL DEFAULT 0 COMMENT '记录状态0-正常 1-异常',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
`school_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属学校ID',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_task_id`(`task_id` ASC) USING BTREE,
INDEX `idx_course_id`(`course_id` ASC) USING BTREE,
INDEX `idx_classroom_id`(`classroom_id` ASC) USING BTREE,
INDEX `idx_att_date`(`att_date` ASC) USING BTREE,
INDEX `idx_school_id`(`school_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '考勤记录汇总表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of att_record
-- ----------------------------
INSERT INTO `att_record` VALUES (1, 1, 1, 1, 4, '2024-06-01', '2024-06-01 08:00:00', '2024-06-01 09:40:00', 45, 44, 1, 0, 0, 97.78, 2.22, 0, NULL, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `att_record` VALUES (2, 2, 2, 2, 2, '2024-06-01', '2024-06-01 10:00:00', '2024-06-01 11:40:00', 38, 38, 0, 0, 0, 100.00, 0.00, 0, NULL, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `att_record` VALUES (3, 3, 3, 3, 3, '2024-06-01', '2024-06-01 14:00:00', '2024-06-01 15:40:00', 52, 49, 3, 0, 0, 94.23, 5.77, 0, NULL, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `att_record` VALUES (4, 4, 4, 4, 4, '2024-05-31', '2024-05-31 08:00:00', '2024-05-31 09:40:00', 40, 38, 2, 0, 0, 95.00, 5.00, 0, NULL, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `att_record` VALUES (5, 5, 5, 5, 5, '2024-05-31', '2024-05-31 10:00:00', '2024-05-31 11:40:00', 120, 108, 12, 0, 0, 90.00, 10.00, 0, NULL, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
-- ----------------------------
-- Table structure for att_rule
-- ----------------------------
DROP TABLE IF EXISTS `att_rule`;
CREATE TABLE `att_rule` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`rule_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '规则名称',
`rule_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '规则类型course-课程/room-教室/global-全局',
`target_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '关联目标ID',
`check_in_start` time NOT NULL COMMENT '签到开始时间课前N分钟',
`check_in_end` time NOT NULL COMMENT '签到截止时间课后N分钟',
`late_threshold` int NOT NULL DEFAULT 15 COMMENT '迟到阈值(分钟)',
`absent_threshold` int NOT NULL DEFAULT 30 COMMENT '缺勤阈值(分钟)',
`early_leave_threshold` int NOT NULL DEFAULT 15 COMMENT '早退阈值(分钟)',
`allow_make_up` tinyint NOT NULL DEFAULT 0 COMMENT '是否允许补签0-否 1-是',
`make_up_limit` int NULL DEFAULT NULL COMMENT '补签时限(小时)',
`school_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属学校ID',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_rule_type`(`rule_type` ASC) USING BTREE,
INDEX `idx_target_id`(`target_id` ASC) USING BTREE,
INDEX `idx_school_id`(`school_id` ASC) USING BTREE,
INDEX `idx_status`(`status` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '考勤规则表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of att_rule
-- ----------------------------
INSERT INTO `att_rule` VALUES (1, '默认考勤规则', 'global', NULL, '00:10:00', '00:15:00', 15, 30, 15, 1, 24, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `att_rule` VALUES (2, '严格考勤规则', 'global', NULL, '00:05:00', '00:10:00', 5, 15, 5, 0, NULL, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
-- ----------------------------
-- Table structure for att_task
-- ----------------------------
DROP TABLE IF EXISTS `att_task`;
CREATE TABLE `att_task` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`task_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '任务编号',
`schedule_id` bigint UNSIGNED NOT NULL COMMENT '课程安排ID',
`course_id` bigint UNSIGNED NOT NULL COMMENT '课程ID',
`course_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '课程名称',
`classroom_id` bigint UNSIGNED NOT NULL COMMENT '教室ID',
`classroom_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '教室名称',
`teacher_id` bigint UNSIGNED NOT NULL COMMENT '教师ID',
`teacher_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '教师姓名',
`class_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '班级ID',
`att_date` date NOT NULL COMMENT '考勤日期',
`start_time` datetime NOT NULL COMMENT '上课开始时间',
`end_time` datetime NOT NULL COMMENT '上课结束时间',
`total_count` int NOT NULL DEFAULT 0 COMMENT '应到人数',
`actual_count` int NOT NULL DEFAULT 0 COMMENT '实到人数',
`absent_count` int NOT NULL DEFAULT 0 COMMENT '缺勤人数',
`late_count` int NOT NULL DEFAULT 0 COMMENT '迟到人数',
`leave_early_count` int NOT NULL DEFAULT 0 COMMENT '早退人数',
`attendance_rate` decimal(5, 2) NULL DEFAULT NULL COMMENT '出勤率%',
`task_status` tinyint NOT NULL DEFAULT 0 COMMENT '任务状态0-未开始 1-进行中 2-已结束 3-已取消',
`school_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属学校ID',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_task_no`(`task_no` ASC) USING BTREE,
INDEX `idx_schedule_id`(`schedule_id` ASC) USING BTREE,
INDEX `idx_course_id`(`course_id` ASC) USING BTREE,
INDEX `idx_classroom_id`(`classroom_id` ASC) USING BTREE,
INDEX `idx_teacher_id`(`teacher_id` ASC) USING BTREE,
INDEX `idx_att_date`(`att_date` ASC) USING BTREE,
INDEX `idx_task_status`(`task_status` ASC) USING BTREE,
INDEX `idx_school_id`(`school_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '考勤任务表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of att_task
-- ----------------------------
INSERT INTO `att_task` VALUES (1, 'ATT202406010001', 1, 1, '高等数学A', 1, '301教室', 4, '赵教授', 3, '2026-06-01', '2026-06-01 08:00:00', '2024-06-01 09:40:00', 45, 44, 1, 0, 0, 97.78, 2, 1, '2026-06-01 17:11:42', '2026-06-03 14:35:40', 0);
INSERT INTO `att_task` VALUES (2, 'ATT202406010002', 2, 2, '大学英语B', 2, '205教室', 2, '李老师', 4, '2026-06-01', '2026-06-01 10:00:00', '2024-06-01 11:40:00', 38, 38, 0, 0, 0, 100.00, 2, 1, '2026-06-01 17:11:42', '2026-06-03 14:35:48', 0);
INSERT INTO `att_task` VALUES (3, 'ATT202406010003', 3, 3, '计算机导论', 3, '102实验室', 3, '张教授', 1, '2026-06-01', '2026-06-01 14:00:00', '2024-06-01 15:40:00', 52, 49, 3, 0, 0, 94.23, 2, 1, '2026-06-01 17:11:42', '2026-06-03 14:35:55', 0);
INSERT INTO `att_task` VALUES (4, 'ATT202405310001', 4, 4, '线性代数', 4, '408教室', 4, '赵教授', 3, '2026-06-01', '2026-06-01 08:00:00', '2024-05-31 09:40:00', 40, 38, 2, 0, 0, 95.00, 2, 1, '2026-06-01 17:11:42', '2026-06-03 14:38:03', 0);
INSERT INTO `att_task` VALUES (5, 'ATT202405310002', 5, 5, '马克思原理', 5, '大阶梯教室', 5, '刘老师', 5, '2026-06-01', '2026-06-01 10:00:00', '2024-05-31 11:40:00', 120, 108, 12, 0, 0, 90.00, 2, 1, '2026-06-01 17:11:42', '2026-06-03 14:38:10', 0);
-- ----------------------------
-- Table structure for behavior_record
-- ----------------------------
DROP TABLE IF EXISTS `behavior_record`;
CREATE TABLE `behavior_record` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`task_id` bigint UNSIGNED NOT NULL COMMENT '考勤任务ID',
`course_id` bigint UNSIGNED NOT NULL COMMENT '课程ID',
`classroom_id` bigint UNSIGNED NOT NULL COMMENT '教室ID',
`student_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '学生ID可为空表示整体统计',
`behavior_type_id` bigint UNSIGNED NOT NULL COMMENT '行为类型ID',
`behavior_time` datetime NOT NULL COMMENT '行为发生时间',
`duration` int NULL DEFAULT NULL COMMENT '持续时间(秒)',
`confidence` decimal(5, 2) NULL DEFAULT NULL COMMENT 'AI识别置信度',
`snapshot_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '行为抓拍图片',
`is_warning` tinyint NOT NULL DEFAULT 0 COMMENT '是否预警0-否 1-是',
`warning_level` tinyint NULL DEFAULT NULL COMMENT '预警级别1-低 2-中 3-高',
`handled` tinyint NOT NULL DEFAULT 0 COMMENT '是否已处理0-否 1-是',
`handler_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '处理人ID',
`handle_time` datetime NULL DEFAULT NULL COMMENT '处理时间',
`handle_remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '处理备注',
`school_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属学校ID',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_task_id`(`task_id` ASC) USING BTREE,
INDEX `idx_course_id`(`course_id` ASC) USING BTREE,
INDEX `idx_student_id`(`student_id` ASC) USING BTREE,
INDEX `idx_behavior_type_id`(`behavior_type_id` ASC) USING BTREE,
INDEX `idx_behavior_time`(`behavior_time` ASC) USING BTREE,
INDEX `idx_is_warning`(`is_warning` ASC) USING BTREE,
INDEX `idx_school_id`(`school_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '课堂行为记录表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of behavior_record
-- ----------------------------
INSERT INTO `behavior_record` VALUES (1, 1, 1, 1, NULL, 1, '2026-06-01 08:15:00', 600, 0.95, NULL, 0, NULL, 0, NULL, NULL, NULL, 1, '2026-06-01 17:11:42', 0, '2026-06-03 11:33:04');
INSERT INTO `behavior_record` VALUES (2, 1, 1, 1, NULL, 1, '2026-06-01 08:30:00', 900, 0.92, NULL, 0, NULL, 0, NULL, NULL, NULL, 1, '2026-06-01 17:11:42', 0, '2026-06-03 11:33:08');
INSERT INTO `behavior_record` VALUES (3, 1, 1, 1, NULL, 2, '2026-06-01 08:45:00', 120, 0.88, NULL, 0, NULL, 0, NULL, NULL, NULL, 1, '2026-06-01 17:11:42', 0, '2026-06-03 11:33:16');
INSERT INTO `behavior_record` VALUES (4, 1, 1, 1, NULL, 3, '2026-06-01 09:00:00', 300, 0.85, NULL, 0, NULL, 0, NULL, NULL, NULL, 1, '2026-06-01 17:11:42', 0, '2026-06-03 11:33:20');
INSERT INTO `behavior_record` VALUES (5, 1, 1, 1, NULL, 4, '2026-06-01 09:10:00', 180, 0.78, NULL, 1, 1, 0, NULL, NULL, NULL, 1, '2026-06-01 17:11:42', 0, '2026-06-03 11:33:23');
INSERT INTO `behavior_record` VALUES (6, 1, 1, 1, NULL, 7, '2026-06-01 09:20:00', 60, 0.65, NULL, 0, NULL, 0, NULL, NULL, NULL, 1, '2026-06-01 17:11:42', 0, '2026-06-03 11:33:27');
INSERT INTO `behavior_record` VALUES (7, 2, 2, 2, NULL, 1, '2026-06-01 10:15:00', 1200, 0.96, NULL, 0, NULL, 0, NULL, NULL, NULL, 1, '2026-06-01 17:11:42', 0, '2026-06-03 11:33:31');
INSERT INTO `behavior_record` VALUES (8, 2, 2, 2, NULL, 2, '2026-06-01 10:30:00', 180, 0.90, NULL, 0, NULL, 0, NULL, NULL, NULL, 1, '2026-06-01 17:11:42', 0, '2026-06-03 11:33:33');
INSERT INTO `behavior_record` VALUES (9, 2, 2, 2, NULL, 3, '2026-06-01 11:00:00', 600, 0.82, NULL, 0, NULL, 0, NULL, NULL, NULL, 1, '2026-06-01 17:11:42', 0, '2026-06-03 11:33:37');
INSERT INTO `behavior_record` VALUES (10, 3, 3, 3, NULL, 1, '2026-06-01 14:10:00', 800, 0.94, NULL, 0, NULL, 0, NULL, NULL, NULL, 1, '2026-06-01 17:11:42', 0, '2026-06-03 11:33:40');
INSERT INTO `behavior_record` VALUES (11, 3, 3, 3, NULL, 4, '2026-06-01 14:30:00', 240, 0.80, NULL, 1, 1, 0, NULL, NULL, NULL, 1, '2026-06-01 17:11:42', 0, '2026-06-03 11:33:43');
INSERT INTO `behavior_record` VALUES (12, 3, 3, 3, NULL, 5, '2026-06-01 14:50:00', 300, 0.75, NULL, 1, 2, 0, NULL, NULL, NULL, 1, '2026-06-01 17:11:42', 0, '2026-06-03 11:33:47');
-- ----------------------------
-- Table structure for behavior_type
-- ----------------------------
DROP TABLE IF EXISTS `behavior_type`;
CREATE TABLE `behavior_type` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`type_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '类型编码',
`type_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '类型名称',
`category` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '分类positive-积极/neutral-中性/negative-消极',
`color` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '图表颜色',
`sort_order` int NOT NULL DEFAULT 0 COMMENT '排序',
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '描述',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_type_code`(`type_code` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '行为类型表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of behavior_type
-- ----------------------------
INSERT INTO `behavior_type` VALUES (1, 'focus', '专注听讲', 'positive', '#52c41a', 1, '学生专注听课', 1, '2026-06-01 17:11:42', 0, '2026-06-02 17:16:38');
INSERT INTO `behavior_type` VALUES (2, 'raise_hand', '举手互动', 'positive', '#1890ff', 2, '学生举手发言或提问', 1, '2026-06-01 17:11:42', 0, '2026-06-02 17:16:38');
INSERT INTO `behavior_type` VALUES (3, 'writing', '低头书写', 'neutral', '#722ed1', 3, '学生低头做笔记或写作业', 1, '2026-06-01 17:11:42', 0, '2026-06-02 17:16:38');
INSERT INTO `behavior_type` VALUES (4, 'talking', '交谈讨论', 'neutral', '#faad14', 4, '学生与旁边同学交谈', 1, '2026-06-01 17:11:42', 0, '2026-06-02 17:16:38');
INSERT INTO `behavior_type` VALUES (5, 'sleeping', '趴桌睡觉', 'negative', '#f5222d', 5, '学生趴在桌上睡觉', 1, '2026-06-01 17:11:42', 0, '2026-06-02 17:16:38');
INSERT INTO `behavior_type` VALUES (6, 'phone', '使用手机', 'negative', '#ff4d4f', 6, '学生在课堂上使用手机', 1, '2026-06-01 17:11:42', 0, '2026-06-02 17:16:38');
INSERT INTO `behavior_type` VALUES (7, 'other', '其他', 'neutral', '#bfbfbf', 99, '其他未分类行为', 1, '2026-06-01 17:11:42', 0, '2026-06-02 17:16:38');
-- ----------------------------
-- Table structure for device
-- ----------------------------
DROP TABLE IF EXISTS `device`;
CREATE TABLE `device` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`device_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '设备编号',
`device_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '设备名称',
`device_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '设备类型camera-摄像头/face_recognition-人脸识别终端/nvr-NVR',
`brand` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '品牌',
`model` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '型号',
`serial_no` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '序列号',
`ip_address` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'IP地址',
`port` int NULL DEFAULT NULL COMMENT '端口',
`mac_address` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'MAC地址',
`classroom_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '安装教室ID',
`location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '安装位置描述',
`firmware_version` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '固件版本',
`last_heartbeat` datetime NULL DEFAULT NULL COMMENT '最后心跳时间',
`online_status` tinyint NOT NULL DEFAULT 0 COMMENT '在线状态0-离线 1-在线',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '使用状态0-停用 1-正常 2-维修中',
`school_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属学校ID',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_device_no`(`device_no` ASC) USING BTREE,
INDEX `idx_classroom_id`(`classroom_id` ASC) USING BTREE,
INDEX `idx_device_type`(`device_type` ASC) USING BTREE,
INDEX `idx_online_status`(`online_status` ASC) USING BTREE,
INDEX `idx_school_id`(`school_id` ASC) USING BTREE,
INDEX `idx_status`(`status` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '设备表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of device
-- ----------------------------
INSERT INTO `device` VALUES (1, 'CAM001', '301教室摄像头', 'camera', '海康威视', 'DS-2CD3T86FWDV2', NULL, '192.168.1.101', 80, NULL, 1, '301教室前方', NULL, NULL, 1, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `device` VALUES (2, 'CAM002', '205教室摄像头', 'camera', '海康威视', 'DS-2CD3T86FWDV2', NULL, '192.168.1.102', 80, NULL, 2, '205教室前方', NULL, NULL, 1, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `device` VALUES (3, 'CAM003', '102实验室摄像头', 'camera', '大华', 'DH-IPC-HFW4631M', NULL, '192.168.1.103', 80, NULL, 3, '102实验室后方', NULL, NULL, 1, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `device` VALUES (4, 'CAM004', '408教室摄像头', 'camera', '海康威视', 'DS-2CD3T86FWDV2', NULL, '192.168.1.104', 80, NULL, 4, '408教室前方', NULL, NULL, 0, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `device` VALUES (5, 'CAM005', '大阶梯教室摄像头', 'camera', '大华', 'DH-IPC-HFW4631M', NULL, '192.168.1.105', 80, NULL, 5, '大阶梯教室中央', NULL, NULL, 1, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `device` VALUES (6, 'FACE001', '301人脸识别终端', 'face_recognition', '旷视', 'FaceID-Pro', NULL, '192.168.1.201', 8080, NULL, 1, '301教室门口', NULL, NULL, 1, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `device` VALUES (7, 'FACE002', '205人脸识别终端', 'face_recognition', '旷视', 'FaceID-Pro', NULL, '192.168.1.202', 8080, NULL, 2, '205教室门口', NULL, NULL, 1, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
-- ----------------------------
-- Table structure for edu_class
-- ----------------------------
DROP TABLE IF EXISTS `edu_class`;
CREATE TABLE `edu_class` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`class_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '班级名称',
`class_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '班级编码',
`grade` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '年级',
`major` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '专业',
`school_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属学校ID',
`student_count` int NOT NULL DEFAULT 0 COMMENT '学生人数',
`headteacher_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '班主任ID教师',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_class_code`(`class_code` ASC) USING BTREE,
INDEX `idx_school_id`(`school_id` ASC) USING BTREE,
INDEX `idx_status`(`status` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '班级表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of edu_class
-- ----------------------------
INSERT INTO `edu_class` VALUES (1, '计算机科学与技术1班', 'CS202401', '2024级', '计算机科学与技术', 1, 52, NULL, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_class` VALUES (2, '软件工程2班', 'SE202402', '2024级', '软件工程', 1, 45, NULL, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_class` VALUES (3, '数学与应用数学1班', 'MA202401', '2024级', '数学与应用数学', 1, 40, NULL, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_class` VALUES (4, '英语师范1班', 'EN202401', '2024级', '英语', 1, 38, NULL, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_class` VALUES (5, '思想政治教育1班', 'PE202401', '2024级', '思想政治教育', 1, 120, NULL, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
-- ----------------------------
-- Table structure for edu_building
-- ----------------------------
DROP TABLE IF EXISTS `edu_building`;
CREATE TABLE `edu_building` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`building_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '教学楼名称',
`building_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '教学楼编码',
`floors` int NULL DEFAULT NULL COMMENT '楼层数',
`school_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属学校ID',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态0-停用 1-正常',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_school_id`(`school_id` ASC) USING BTREE,
INDEX `idx_status`(`status` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '教学楼表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of edu_building
-- ----------------------------
INSERT INTO `edu_building` VALUES (1, '第一教学楼', 'BLD-A', 5, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_building` VALUES (2, '第二教学楼', 'BLD-B', 6, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_building` VALUES (3, '实验楼', 'BLD-C', 3, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
-- ----------------------------
-- Table structure for edu_classroom
-- ----------------------------
DROP TABLE IF EXISTS `edu_classroom`;
CREATE TABLE `edu_classroom` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`room_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '教室编号',
`room_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '教室名称',
`building_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属教学楼ID',
`floor` int NULL DEFAULT NULL COMMENT '楼层',
`capacity` int NULL DEFAULT NULL COMMENT '容纳人数',
`room_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '普通教室' COMMENT '教室类型',
`camera_device_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '关联摄像头设备ID',
`school_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属学校ID',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态0-停用 1-正常',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_room_no`(`room_no` ASC) USING BTREE,
INDEX `idx_building_id`(`building_id` ASC) USING BTREE,
INDEX `idx_school_id`(`school_id` ASC) USING BTREE,
INDEX `idx_status`(`status` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '教室表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of edu_classroom
-- ----------------------------
INSERT INTO `edu_classroom` VALUES (1, '301', '301教室', 1, 3, 50, '普通教室', NULL, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_classroom` VALUES (2, '205', '205教室', 1, 2, 45, '普通教室', NULL, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_classroom` VALUES (3, '102', '102实验室', 3, 1, 55, '实验室', NULL, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_classroom` VALUES (4, '408', '408教室', 2, 4, 48, '普通教室', NULL, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_classroom` VALUES (5, 'DJT', '大阶梯教室', 1, 1, 150, '阶梯教室', NULL, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_classroom` VALUES (6, '302', '302教室', 1, 3, 50, '普通教室', NULL, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_classroom` VALUES (7, '303', '303教室', 1, 3, 50, '普通教室', NULL, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_classroom` VALUES (8, '304', '304教室', 1, 3, 50, '普通教室', NULL, 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
-- ----------------------------
-- Table structure for edu_course
-- ----------------------------
DROP TABLE IF EXISTS `edu_course`;
CREATE TABLE `edu_course` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`course_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '课程编码',
`course_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '课程名称',
`course_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '课程类型',
`credit` decimal(3, 1) NULL DEFAULT NULL COMMENT '学分',
`teacher_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '授课教师ID',
`school_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属学校ID',
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '课程描述',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_course_code`(`course_code` ASC) USING BTREE,
INDEX `idx_teacher_id`(`teacher_id` ASC) USING BTREE,
INDEX `idx_school_id`(`school_id` ASC) USING BTREE,
INDEX `idx_status`(`status` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '课程表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of edu_course
-- ----------------------------
INSERT INTO `edu_course` VALUES (1, 'CS101', '高等数学A', '必修课', 4.0, 4, 1, '高等数学基础课程', 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_course` VALUES (2, 'EN101', '大学英语B', '必修课', 3.0, 2, 1, '大学英语基础课程', 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_course` VALUES (3, 'CS102', '计算机导论', '必修课', 2.0, 3, 1, '计算机专业入门课程', 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_course` VALUES (4, 'MA101', '线性代数', '必修课', 3.0, 4, 1, '线性代数基础课程', 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_course` VALUES (5, 'PE101', '马克思原理', '必修课', 3.0, 5, 1, '马克思主义基本原理', 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
-- ----------------------------
-- Table structure for edu_course_schedule
-- ----------------------------
DROP TABLE IF EXISTS `edu_course_schedule`;
CREATE TABLE `edu_course_schedule` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`course_id` bigint UNSIGNED NOT NULL COMMENT '课程ID',
`classroom_id` bigint UNSIGNED NOT NULL COMMENT '教室ID',
`class_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '上课班级ID',
`teacher_id` bigint UNSIGNED NOT NULL COMMENT '授课教师ID',
`week_day` tinyint NOT NULL COMMENT '星期几1-7',
`start_section` tinyint NOT NULL COMMENT '开始节次',
`end_section` tinyint NOT NULL COMMENT '结束节次',
`start_week` int NOT NULL DEFAULT 1 COMMENT '开始周',
`end_week` int NOT NULL DEFAULT 16 COMMENT '结束周',
`semester` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '学期2024-2025-1',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_course_id`(`course_id` ASC) USING BTREE,
INDEX `idx_classroom_id`(`classroom_id` ASC) USING BTREE,
INDEX `idx_teacher_id`(`teacher_id` ASC) USING BTREE,
INDEX `idx_class_id`(`class_id` ASC) USING BTREE,
INDEX `idx_week_day`(`week_day` ASC) USING BTREE,
INDEX `idx_semester`(`semester` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '课程安排表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of edu_course_schedule
-- ----------------------------
-- ----------------------------
-- Table structure for edu_student
-- ----------------------------
DROP TABLE IF EXISTS `edu_student`;
CREATE TABLE `edu_student` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`student_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '学号',
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '姓名',
`gender` tinyint NULL DEFAULT NULL COMMENT '性别0-女 1-男',
`avatar` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '头像URL',
`face_feature` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '人脸特征值Base64',
`face_image` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '人脸照片URL',
`class_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属班级ID',
`school_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属学校ID',
`phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '手机号',
`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '邮箱',
`user_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '关联系统用户ID',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态0-离校 1-在读',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_student_no`(`student_no` ASC) USING BTREE,
INDEX `idx_class_id`(`class_id` ASC) USING BTREE,
INDEX `idx_school_id`(`school_id` ASC) USING BTREE,
INDEX `idx_user_id`(`user_id` ASC) USING BTREE,
INDEX `idx_status`(`status` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '学生表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of edu_student
-- ----------------------------
-- ----------------------------
-- Table structure for edu_teacher
-- ----------------------------
DROP TABLE IF EXISTS `edu_teacher`;
CREATE TABLE `edu_teacher` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`teacher_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '工号',
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '姓名',
`gender` tinyint NULL DEFAULT NULL COMMENT '性别0-女 1-男',
`avatar` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '头像URL',
`title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '职称',
`department` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '所属院系',
`school_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属学校ID',
`phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '手机号',
`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '邮箱',
`user_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '关联系统用户ID',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_teacher_no`(`teacher_no` ASC) USING BTREE,
INDEX `idx_school_id`(`school_id` ASC) USING BTREE,
INDEX `idx_user_id`(`user_id` ASC) USING BTREE,
INDEX `idx_status`(`status` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '教师表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of edu_teacher
-- ----------------------------
INSERT INTO `edu_teacher` VALUES (1, 'T001', '王教授', 1, NULL, '教授', '计算机学院', 1, '13900139001', 'wang@school.edu', NULL, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_teacher` VALUES (2, 'T002', '李老师', 0, NULL, '讲师', '外国语学院', 1, '13900139002', 'li@school.edu', NULL, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_teacher` VALUES (3, 'T003', '张教授', 1, NULL, '教授', '计算机学院', 1, '13900139003', 'zhang@school.edu', NULL, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_teacher` VALUES (4, 'T004', '赵教授', 1, NULL, '副教授', '数学学院', 1, '13900139004', 'zhao@school.edu', NULL, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `edu_teacher` VALUES (5, 'T005', '刘老师', 0, NULL, '讲师', '马克思主义学院', 1, '13900139005', 'liu@school.edu', NULL, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
-- ----------------------------
-- Table structure for monitor_record
-- ----------------------------
DROP TABLE IF EXISTS `monitor_record`;
CREATE TABLE `monitor_record` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`classroom_id` bigint UNSIGNED NOT NULL COMMENT '教室ID',
`device_id` bigint UNSIGNED NOT NULL COMMENT '监控设备ID',
`record_date` date NOT NULL COMMENT '记录日期',
`start_time` datetime NOT NULL COMMENT '开始时间',
`end_time` datetime NULL DEFAULT NULL COMMENT '结束时间',
`stream_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '直播流地址',
`record_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '录像地址',
`file_size` bigint NULL DEFAULT NULL COMMENT '录像文件大小(字节)',
`record_status` tinyint NOT NULL DEFAULT 1 COMMENT '记录状态0-失败 1-成功',
`school_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属学校ID',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_classroom_id`(`classroom_id` ASC) USING BTREE,
INDEX `idx_device_id`(`device_id` ASC) USING BTREE,
INDEX `idx_record_date`(`record_date` ASC) USING BTREE,
INDEX `idx_school_id`(`school_id` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '教室监控记录表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of monitor_record
-- ----------------------------
-- ----------------------------
-- Table structure for sys_operation_log
-- ----------------------------
DROP TABLE IF EXISTS `sys_operation_log`;
CREATE TABLE `sys_operation_log` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '操作用户ID',
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '操作用户名',
`module` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '操作模块',
`action` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '操作动作',
`method` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '请求方法',
`request_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '请求URL',
`request_params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '请求参数',
`response_data` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '响应数据',
`ip_address` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'IP地址',
`user_agent` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'User-Agent',
`duration` int NULL DEFAULT NULL COMMENT '执行时长(毫秒)',
`status` tinyint NULL DEFAULT NULL COMMENT '状态0-失败 1-成功',
`error_msg` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '错误信息',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_user_id`(`user_id` ASC) USING BTREE,
INDEX `idx_module`(`module` ASC) USING BTREE,
INDEX `idx_created_at`(`created_at` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统操作日志表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_operation_log
-- ----------------------------
-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`perm_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '权限编码',
`perm_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '权限名称',
`perm_type` tinyint NOT NULL DEFAULT 1 COMMENT '权限类型1-菜单 2-按钮 3-接口',
`parent_id` bigint UNSIGNED NULL DEFAULT 0 COMMENT '父权限ID',
`path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '前端路由路径',
`icon` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '图标',
`sort_order` int NOT NULL DEFAULT 0 COMMENT '排序',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_perm_code`(`perm_code` ASC) USING BTREE,
INDEX `idx_parent_id`(`parent_id` ASC) USING BTREE,
INDEX `idx_status`(`status` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '权限表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES (1, 'dashboard', '首页', 1, 0, '/dashboard', 'HomeFilled', 1, 1, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_permission` VALUES (2, 'behavior', '课堂行为分析', 1, 0, '/behavior', 'TrendCharts', 2, 1, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_permission` VALUES (3, 'history', '历史记录查询', 1, 0, '/history', 'Search', 3, 1, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_permission` VALUES (4, 'bigscreen', '数据展示大屏', 1, 0, '/bigscreen', 'DataAnalysis', 4, 1, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_permission` VALUES (5, 'settings', '系统设置', 1, 0, '/settings', 'Setting', 5, 1, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_permission` VALUES (6, 'settings.personnel', '人员管理', 1, 5, '/settings/personnel', 'User', 1, 1, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_permission` VALUES (7, 'settings.device', '设备管理', 1, 5, '/settings/device', 'Monitor', 2, 1, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_permission` VALUES (8, 'settings.rules', '考勤规则设置', 1, 5, '/settings/rules', 'Notebook', 3, 1, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_permission` VALUES (9, 'settings.permissions', '权限管理', 1, 5, '/settings/permissions', 'Lock', 4, 1, '2026-06-01 17:11:42', 0);
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`role_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色编码admin/teacher/staff',
`role_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色名称',
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '角色描述',
`sort_order` int NOT NULL DEFAULT 0 COMMENT '排序',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态0-禁用 1-启用',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_role_code`(`role_code` ASC) USING BTREE,
INDEX `idx_status`(`status` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'admin', '管理员', '系统管理员,拥有全部权限', 1, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role` VALUES (2, 'teacher', '教师', '授课教师,可查看本班/本课程考勤', 2, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role` VALUES (3, 'staff', '教务员', '教务管理人员,可查看和管理考勤数据', 3, 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
`role_id` bigint UNSIGNED NOT NULL COMMENT '角色ID',
`permission_id` bigint UNSIGNED NOT NULL COMMENT '权限ID',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_role_perm`(`role_id` ASC, `permission_id` ASC) USING BTREE,
INDEX `idx_permission_id`(`permission_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色权限关联表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES (1, 1, 1, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role_permission` VALUES (2, 1, 2, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role_permission` VALUES (3, 1, 3, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role_permission` VALUES (4, 1, 4, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role_permission` VALUES (5, 1, 5, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role_permission` VALUES (6, 1, 6, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role_permission` VALUES (7, 1, 7, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role_permission` VALUES (8, 1, 8, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role_permission` VALUES (9, 1, 9, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role_permission` VALUES (16, 3, 1, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role_permission` VALUES (17, 3, 2, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role_permission` VALUES (18, 3, 3, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role_permission` VALUES (19, 3, 6, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role_permission` VALUES (20, 3, 8, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role_permission` VALUES (21, 2, 1, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role_permission` VALUES (22, 2, 2, '2026-06-01 17:11:42', 0);
INSERT INTO `sys_role_permission` VALUES (23, 2, 3, '2026-06-01 17:11:42', 0);
-- ----------------------------
-- Table structure for sys_school
-- ----------------------------
DROP TABLE IF EXISTS `sys_school`;
CREATE TABLE `sys_school` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '学校名称',
`code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '学校编码',
`address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '地址',
`contact_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '联系电话',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态0-禁用 1-启用',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_school_code`(`code` ASC) USING BTREE,
INDEX `idx_status`(`status` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '学校/机构表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_school
-- ----------------------------
INSERT INTO `sys_school` VALUES (1, '阳光实验学校', 'SCHOOL_001', '阳光大道88号', '010-88888888', 1, '2026-06-01 17:11:42', '2026-06-01 17:11:42', 0);
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '登录账号(工号/学号)',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '登录密码BCrypt加密',
`real_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '真实姓名',
`avatar` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '头像URL',
`role_id` bigint UNSIGNED NOT NULL COMMENT '角色ID',
`school_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属学校ID',
`phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '手机号',
`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '邮箱',
`last_login_time` datetime NULL DEFAULT NULL COMMENT '最后登录时间',
`last_login_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '最后登录IP',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态0-禁用 1-启用',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_username`(`username` ASC) USING BTREE,
INDEX `idx_role_id`(`role_id` ASC) USING BTREE,
INDEX `idx_school_id`(`school_id` ASC) USING BTREE,
INDEX `idx_status`(`status` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统用户表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', '管理员', NULL, 1, 1, '13800138001', 'admin@school.edu', '2026-06-03 10:13:06', '10.23.22.43', 1, '2026-06-01 17:11:42', '2026-06-02 17:26:20', 0);
SET FOREIGN_KEY_CHECKS = 1;

@ -0,0 +1,15 @@
package com.attendance;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
@MapperScan("com.attendance.mapper")
public class AttendanceApplication {
public static void main(String[] args) {
SpringApplication.run(AttendanceApplication.class, args);
}
}

@ -0,0 +1,18 @@
package com.attendance.annotation;
import java.lang.annotation.*;
/**
* Controller
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {
/** 操作模块,如:考勤管理、用户管理 */
String module() default "";
/** 操作动作,如:新增用户、删除考勤任务 */
String action() default "";
}

@ -0,0 +1,103 @@
package com.attendance.aspect;
import com.alibaba.fastjson2.JSON;
import com.attendance.annotation.OperationLog;
import com.attendance.common.UserContext;
import com.attendance.entity.SysOperationLog;
import com.attendance.service.SysOperationLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class OperationLogAspect {
private final SysOperationLogService sysOperationLogService;
@Around("@annotation(operationLog)")
public Object around(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable {
long startTime = System.currentTimeMillis();
SysOperationLog logEntity = new SysOperationLog();
logEntity.setUserId(UserContext.getUserId());
logEntity.setUsername(UserContext.getUsername());
logEntity.setModule(operationLog.module());
logEntity.setAction(operationLog.action());
// 填充请求相关字段
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
logEntity.setMethod(request.getMethod());
logEntity.setRequestUrl(request.getRequestURI());
logEntity.setIpAddress(getClientIp(request));
logEntity.setUserAgent(request.getHeader("User-Agent"));
// 记录请求参数(排除文件上传等二进制参数)
Object[] args = joinPoint.getArgs();
try {
logEntity.setRequestParams(JSON.toJSONString(args));
} catch (Exception e) {
logEntity.setRequestParams("[无法序列化的参数]");
}
}
Object result;
try {
result = joinPoint.proceed();
logEntity.setStatus(1);
try {
logEntity.setResponseData(JSON.toJSONString(result));
} catch (Exception e) {
logEntity.setResponseData("[无法序列化的响应]");
}
} catch (Throwable e) {
logEntity.setStatus(0);
logEntity.setErrorMsg(e.getMessage());
throw e;
} finally {
logEntity.setDuration((int) (System.currentTimeMillis() - startTime));
try {
sysOperationLogService.asyncSave(logEntity);
} catch (Exception e) {
log.error("保存操作日志失败", e);
}
}
return result;
}
/** 获取客户端真实IP */
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 多级代理取第一个IP
if (ip != null && ip.contains(",")) {
ip = ip.split(",")[0].trim();
}
return ip;
}
}

@ -0,0 +1,24 @@
package com.attendance.common;
import lombok.Getter;
@Getter
public class BusinessException extends RuntimeException {
private final Integer code;
public BusinessException(String message) {
super(message);
this.code = ResultCode.ERROR.getCode();
}
public BusinessException(ResultCode resultCode) {
super(resultCode.getMessage());
this.code = resultCode.getCode();
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
}

@ -0,0 +1,36 @@
package com.attendance.common;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
log.warn("业务异常: {}", e.getMessage());
return Result.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(BindException.class)
public Result<Void> handleBindException(BindException e) {
String message = e.getAllErrors().get(0).getDefaultMessage();
log.warn("参数校验失败: {}", message);
return Result.error(ResultCode.PARAM_VALIDATE_FAILED.getCode(), message);
}
@ExceptionHandler(IllegalArgumentException.class)
public Result<Void> handleIllegalArgumentException(IllegalArgumentException e) {
log.warn("参数异常: {}", e.getMessage());
return Result.error(ResultCode.PARAM_ERROR.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统异常", e);
return Result.error("服务器异常,请稍后重试");
}
}

@ -0,0 +1,67 @@
package com.attendance.common;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "统一响应结果")
public class Result<T> {
@Schema(description = "状态码", example = "200")
private Integer code;
@Schema(description = "提示消息", example = "操作成功")
private String message;
@Schema(description = "响应数据")
private T data;
@Schema(description = "时间戳")
private Long timestamp;
public Result() {
this.timestamp = System.currentTimeMillis();
}
public static <T> Result<T> success() {
return success(null);
}
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(ResultCode.SUCCESS.getCode());
result.setMessage(ResultCode.SUCCESS.getMessage());
result.setData(data);
return result;
}
public static <T> Result<T> success(String message, T data) {
Result<T> result = new Result<>();
result.setCode(ResultCode.SUCCESS.getCode());
result.setMessage(message);
result.setData(data);
return result;
}
public static Result<Void> ok(String message) {
Result<Void> result = new Result<>();
result.setCode(ResultCode.SUCCESS.getCode());
result.setMessage(message);
return result;
}
public static <T> Result<T> error(String message) {
return error(ResultCode.ERROR.getCode(), message);
}
public static <T> Result<T> error(ResultCode resultCode) {
return error(resultCode.getCode(), resultCode.getMessage());
}
public static <T> Result<T> error(Integer code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
return result;
}
}

@ -0,0 +1,32 @@
package com.attendance.common;
import lombok.Getter;
@Getter
public enum ResultCode {
SUCCESS(200, "操作成功"),
ERROR(500, "操作失败"),
PARAM_ERROR(400, "参数错误"),
UNAUTHORIZED(401, "未登录或登录已过期"),
FORBIDDEN(403, "没有访问权限"),
NOT_FOUND(404, "资源不存在"),
METHOD_NOT_ALLOWED(405, "请求方式不允许"),
PARAM_VALIDATE_FAILED(422, "参数校验失败"),
USER_NOT_FOUND(1001, "用户不存在"),
USER_PASSWORD_ERROR(1002, "密码错误"),
USER_DISABLED(1003, "账号已被禁用"),
USERNAME_EXISTS(1004, "用户名已存在"),
TOKEN_INVALID(2001, "Token无效"),
TOKEN_EXPIRED(2002, "Token已过期");
private final Integer code;
private final String message;
ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
}

@ -0,0 +1,45 @@
package com.attendance.common;
/**
* ThreadLocal
*/
public class UserContext {
private static final ThreadLocal<UserInfo> THREAD_LOCAL = new ThreadLocal<>();
public static void set(Long userId, String username) {
THREAD_LOCAL.set(new UserInfo(userId, username));
}
public static Long getUserId() {
UserInfo info = THREAD_LOCAL.get();
return info != null ? info.getUserId() : null;
}
public static String getUsername() {
UserInfo info = THREAD_LOCAL.get();
return info != null ? info.getUsername() : null;
}
public static void clear() {
THREAD_LOCAL.remove();
}
private static class UserInfo {
private final Long userId;
private final String username;
UserInfo(Long userId, String username) {
this.userId = userId;
this.username = username;
}
Long getUserId() {
return userId;
}
String getUsername() {
return username;
}
}
}

@ -0,0 +1,61 @@
package com.attendance.config;
import com.alibaba.fastjson2.JSON;
import com.attendance.common.Result;
import com.attendance.common.ResultCode;
import com.attendance.common.UserContext;
import com.attendance.utils.JwtUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.io.IOException;
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtInterceptor implements HandlerInterceptor {
private final JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 放行 CORS 预检请求
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
return true;
}
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
writeError(response, ResultCode.UNAUTHORIZED);
return false;
}
token = token.substring(7);
try {
jwtUtil.verifyToken(token);
// 将用户信息存入上下文,供操作日志等下游使用
UserContext.set(jwtUtil.getUserId(token), jwtUtil.getUsername(token));
return true;
} catch (Exception e) {
log.warn("Token验证失败: {}", e.getMessage());
writeError(response, ResultCode.UNAUTHORIZED);
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 请求结束后清理 ThreadLocal避免内存泄漏
UserContext.clear();
}
private void writeError(HttpServletResponse response, ResultCode resultCode) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(401);
response.getWriter().write(JSON.toJSONString(Result.error(resultCode)));
}
}

@ -0,0 +1,27 @@
package com.attendance.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Knife4jConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("教室智能人脸考勤系统 API")
.description("教室智能人脸考勤系统后端接口文档")
.version("v1.0.0")
.contact(new Contact()
.name("SmartCampus")
.email("admin@school.edu"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0")));
}
}

@ -0,0 +1,18 @@
package com.attendance.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

@ -0,0 +1,25 @@
package com.attendance.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createdAt", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "deleted", Integer.class, 0);
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
}
}

@ -0,0 +1,40 @@
package com.attendance.config;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
private final JwtInterceptor jwtInterceptor;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(
"/auth/login",
"/auth/register",
"/v3/api-docs/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/doc.html",
"/webjars/**",
"/favicon.ico"
);
}
}

@ -0,0 +1,73 @@
package com.attendance.controller;
import com.attendance.annotation.OperationLog;
import com.attendance.common.Result;
import com.attendance.entity.AttRule;
import com.attendance.service.AttRuleService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "考勤规则", description = "考勤规则设置接口")
@RestController
@RequestMapping("/att-rule")
public class AttRuleController {
private final AttRuleService attRuleService;
public AttRuleController(AttRuleService attRuleService) {
this.attRuleService = attRuleService;
}
@Operation(summary = "分页查询考勤规则")
@GetMapping("/page")
public Result<Page<AttRule>> page(
@RequestParam(defaultValue = "1") Long current,
@RequestParam(defaultValue = "10") Long size) {
LambdaQueryWrapper<AttRule> wrapper = new LambdaQueryWrapper<>();
wrapper.orderByDesc(AttRule::getCreatedAt);
return Result.success(attRuleService.page(new Page<>(current, size), wrapper));
}
@Operation(summary = "获取规则详情")
@GetMapping("/{id}")
public Result<AttRule> getById(@PathVariable Long id) {
return Result.success(attRuleService.getById(id));
}
@OperationLog(module = "考勤规则", action = "新增考勤规则")
@Operation(summary = "新增考勤规则")
@PostMapping
public Result<Void> save(@RequestBody AttRule rule) {
attRuleService.save(rule);
return Result.ok("新增成功");
}
@OperationLog(module = "考勤规则", action = "修改考勤规则")
@Operation(summary = "修改考勤规则")
@PutMapping("/{id}")
public Result<Void> update(@PathVariable Long id, @RequestBody AttRule rule) {
rule.setId(id);
attRuleService.updateById(rule);
return Result.ok("修改成功");
}
@OperationLog(module = "考勤规则", action = "删除考勤规则")
@Operation(summary = "删除考勤规则")
@DeleteMapping
public Result<Void> remove(@Parameter(description = "规则ID列表") @RequestBody List<Long> ids) {
attRuleService.removeByIds(ids);
return Result.ok("删除成功");
}
@Operation(summary = "获取全部规则")
@GetMapping("/list")
public Result<List<AttRule>> list() {
return Result.success(attRuleService.list());
}
}

@ -0,0 +1,151 @@
package com.attendance.controller;
import com.attendance.annotation.OperationLog;
import com.attendance.common.Result;
import com.attendance.entity.AttDetail;
import com.attendance.entity.AttRecord;
import com.attendance.entity.AttTask;
import com.attendance.entity.Student;
import com.attendance.service.AttDetailService;
import com.attendance.service.AttRecordService;
import com.attendance.service.AttTaskService;
import com.attendance.vo.AttRecordVO;
import com.attendance.vo.AttTaskVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
@Tag(name = "考勤管理", description = "考勤任务、记录、明细及历史查询接口")
@RestController
@RequestMapping("/attendance")
public class AttendanceController {
private final AttTaskService attTaskService;
private final AttRecordService attRecordService;
private final AttDetailService attDetailService;
public AttendanceController(AttTaskService attTaskService, AttRecordService attRecordService, AttDetailService attDetailService) {
this.attTaskService = attTaskService;
this.attRecordService = attRecordService;
this.attDetailService = attDetailService;
}
@Operation(summary = "分页查询考勤任务")
@GetMapping("/task/page")
public Result<IPage<AttTaskVO>> taskPage(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current,
@Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size,
@Parameter(description = "考勤日期(yyyy-MM-dd)") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate attDate,
@Parameter(description = "关键字(课程名/教室名/教师名)") @RequestParam(required = false) String keyword,
@Parameter(description = "任务状态0-未开始 1-进行中 2-已结束 3-已取消") @RequestParam(required = false) Integer taskStatus) {
Page<AttTaskVO> page = new Page<>(current, size);
return Result.success(attTaskService.getTaskPage(page, attDate, keyword, taskStatus));
}
@Operation(summary = "获取考勤任务详情")
@GetMapping("/task/{id}")
public Result<AttTask> getTaskById(@PathVariable Long id) {
return Result.success(attTaskService.getById(id));
}
@OperationLog(module = "考勤管理", action = "新增考勤任务")
@Operation(summary = "新增考勤任务")
@PostMapping("/task")
public Result<Void> saveTask(@RequestBody AttTask task) {
attTaskService.save(task);
return Result.ok("新增成功");
}
@OperationLog(module = "考勤管理", action = "修改考勤任务")
@Operation(summary = "修改考勤任务")
@PutMapping("/task/{id}")
public Result<Void> updateTask(@PathVariable Long id, @RequestBody AttTask task) {
task.setId(id);
attTaskService.updateById(task);
return Result.ok("修改成功");
}
@OperationLog(module = "考勤管理", action = "删除考勤任务")
@Operation(summary = "删除考勤任务")
@DeleteMapping("/task")
public Result<Void> removeTask(@Parameter(description = "考勤任务ID列表") @RequestBody List<Long> ids) {
attTaskService.removeByIds(ids);
return Result.ok("删除成功");
}
@Operation(summary = "分页查询考勤记录汇总")
@GetMapping("/record/page")
public Result<Page<AttRecord>> recordPage(
@RequestParam(defaultValue = "1") Long current,
@RequestParam(defaultValue = "10") Long size,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate attDate) {
LambdaQueryWrapper<AttRecord> wrapper = new LambdaQueryWrapper<>();
if (attDate != null) {
wrapper.eq(AttRecord::getAttDate, attDate);
}
wrapper.orderByDesc(AttRecord::getAttDate);
return Result.success(attRecordService.page(new Page<>(current, size), wrapper));
}
@Operation(summary = "获取考勤记录详情")
@GetMapping("/record/{id}")
public Result<AttRecord> getRecordById(@PathVariable Long id) {
return Result.success(attRecordService.getById(id));
}
@Operation(summary = "根据任务ID查询考勤明细")
@GetMapping("/detail/{taskId}")
public Result<List<AttDetail>> getDetailsByTaskId(@PathVariable Long taskId) {
return Result.success(attDetailService.getDetailsByTaskId(taskId));
}
@OperationLog(module = "考勤管理", action = "修改考勤明细")
@Operation(summary = "修改考勤明细")
@PutMapping("/detail/{id}")
public Result<Void> updateDetail(@PathVariable Long id, @RequestBody AttDetail detail) {
detail.setId(id);
attDetailService.updateById(detail);
return Result.ok("修改成功");
}
@Operation(summary = "分页查询考勤明细")
@GetMapping("/detail/page")
public Result<Page<AttDetail>> detailPage(
@RequestParam(defaultValue = "1") Long current,
@RequestParam(defaultValue = "10") Long size,
@RequestParam(required = false) Long taskId,
@RequestParam(required = false) String studentName,
@RequestParam(required = false) Integer attStatus) {
LambdaQueryWrapper<AttDetail> wrapper = new LambdaQueryWrapper<>();
if (studentName != null && !studentName.isEmpty()) {
wrapper.like(AttDetail::getStudentName, studentName);
}
if (attStatus != null) {
wrapper.eq(AttDetail::getAttStatus, attStatus);
}
if (taskId != null) {
wrapper.eq(AttDetail::getTaskId, taskId);
}
return Result.success(attDetailService.page(new Page<>(current, size), wrapper));
}
@Operation(summary = "历史考勤查询")
@GetMapping("/history")
public Result<IPage<AttRecordVO>> history(
@RequestParam(defaultValue = "1") Long current,
@RequestParam(defaultValue = "10") Long size,
@Parameter(description = "课程名称") @RequestParam(required = false) String courseName,
@Parameter(description = "考勤日期(yyyy-MM-dd)") @RequestParam(required = false) String attDate) {
Page<AttRecordVO> page = new Page<>(current, size);
return Result.success(attRecordService.getHistoryPage(page, courseName, attDate));
}
}

@ -0,0 +1,35 @@
package com.attendance.controller;
import com.attendance.common.Result;
import com.attendance.dto.LoginDTO;
import com.attendance.dto.LoginResultDTO;
import com.attendance.service.SysUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@Tag(name = "认证管理", description = "用户登录相关接口")
@RestController
@RequestMapping("/auth")
public class AuthController {
private final SysUserService sysUserService;
public AuthController(SysUserService sysUserService) {
this.sysUserService = sysUserService;
}
@Operation(summary = "用户登录")
@PostMapping("/login")
public Result<LoginResultDTO> login(@Valid @RequestBody LoginDTO loginDTO) {
return Result.success(sysUserService.login(loginDTO));
}
@Operation(summary = "用户登出")
@PostMapping("/logout")
public Result<Void> logout() {
return Result.success();
}
}

@ -0,0 +1,140 @@
package com.attendance.controller;
import com.attendance.annotation.OperationLog;
import com.attendance.common.Result;
import com.attendance.vo.BehaviorRecordVO;
import com.attendance.entity.BehaviorRecord;
import com.attendance.entity.BehaviorType;
import com.attendance.service.BehaviorRecordService;
import com.attendance.service.BehaviorTypeService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Tag(name = "课堂行为分析", description = "课堂行为识别与统计接口")
@RestController
@RequestMapping("/behavior")
public class BehaviorController {
private final BehaviorRecordService behaviorRecordService;
private final BehaviorTypeService behaviorTypeService;
public BehaviorController(BehaviorRecordService behaviorRecordService, BehaviorTypeService behaviorTypeService) {
this.behaviorRecordService = behaviorRecordService;
this.behaviorTypeService = behaviorTypeService;
}
@Operation(summary = "分页查询行为记录")
@GetMapping("/page")
public Result<Page<BehaviorRecord>> page(
@RequestParam(defaultValue = "1") Long current,
@RequestParam(defaultValue = "10") Long size,
@RequestParam(required = false) Long taskId,
@RequestParam(required = false) Long behaviorTypeId) {
LambdaQueryWrapper<BehaviorRecord> wrapper = new LambdaQueryWrapper<>();
if (taskId != null) {
wrapper.eq(BehaviorRecord::getTaskId, taskId);
}
if (behaviorTypeId != null) {
wrapper.eq(BehaviorRecord::getBehaviorTypeId, behaviorTypeId);
}
wrapper.orderByDesc(BehaviorRecord::getBehaviorTime);
return Result.success(behaviorRecordService.page(new Page<>(current, size), wrapper));
}
@Operation(summary = "获取行为类型列表")
@GetMapping("/types")
public Result<List<BehaviorType>> getTypes() {
return Result.success(behaviorTypeService.list(
new LambdaQueryWrapper<BehaviorType>().eq(BehaviorType::getStatus, 1).orderByAsc(BehaviorType::getSortOrder)));
}
@Operation(summary = "获取行为类型列表(含统计次数,支持筛选)")
@GetMapping("/types-with-stats")
public Result<List<BehaviorType>> getTypesWithStats(
@RequestParam(required = false) @Parameter(description = "课程ID") Long courseId,
@RequestParam(required = false) @Parameter(description = "教师ID") Long teacherId,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") @Parameter(description = "考勤日期格式yyyy-MM-dd") LocalDate attDate) {
List<BehaviorType> types = behaviorTypeService.list(
new LambdaQueryWrapper<BehaviorType>().eq(BehaviorType::getStatus, 1).orderByAsc(BehaviorType::getSortOrder));
List<Map<String, Object>> stats = behaviorRecordService.getBehaviorStatsWithFilters(courseId, teacherId, attDate);
Map<String, Object> statsMap = stats.stream()
.collect(Collectors.toMap(
m -> String.valueOf(m.get("name")),
m -> m.get("value"),
(v1, v2) -> v1));
types.forEach(t -> t.setCount(((Number) statsMap.getOrDefault(t.getTypeName(), 0)).intValue()));
return Result.success(types);
}
@Operation(summary = "获取某次课的行为统计")
@GetMapping("/stats/{taskId}")
public Result<List<Map<String, Object>>> getStats(@PathVariable Long taskId) {
return Result.success(behaviorRecordService.getBehaviorStatsByTaskId(taskId));
}
@Operation(summary = "按时段统计行为次数08:00-22:00每2小时一个区间")
@GetMapping("/stats/time-period")
public Result<List<Map<String, Object>>> getTimePeriodStats(
@RequestParam(required = false) @Parameter(description = "课程ID") Long courseId,
@RequestParam(required = false) @Parameter(description = "教师ID") Long teacherId,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") @Parameter(description = "考勤日期格式yyyy-MM-dd") LocalDate attDate) {
return Result.success(behaviorRecordService.getTimePeriodStats(courseId, teacherId, attDate));
}
@Operation(summary = "分页查询课堂行为记录(支持课程、教师、时间、行为类型筛选)")
@GetMapping("/records")
public Result<IPage<BehaviorRecordVO>> records(
@RequestParam(defaultValue = "1") @Parameter(description = "当前页") Long current,
@RequestParam(defaultValue = "10") @Parameter(description = "每页条数") Long size,
@RequestParam(required = false) @Parameter(description = "课程ID") Long courseId,
@RequestParam(required = false) @Parameter(description = "教师ID") Long teacherId,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") @Parameter(description = "考勤日期格式yyyy-MM-dd") LocalDate attDate,
@RequestParam(required = false) @Parameter(description = "行为类型ID") Long behaviorTypeId) {
Page<BehaviorRecordVO> page = new Page<>(current, size);
return Result.success(behaviorRecordService.getBehaviorRecordPage(page, courseId, teacherId, attDate, behaviorTypeId));
}
@OperationLog(module = "课堂行为", action = "新增行为记录")
@Operation(summary = "新增行为记录")
@PostMapping
public Result<Void> save(@RequestBody BehaviorRecord record) {
behaviorRecordService.save(record);
return Result.ok("新增成功");
}
@OperationLog(module = "课堂行为", action = "标记行为已处理")
@Operation(summary = "标记行为已处理")
@PutMapping("/{id}/handle")
public Result<Void> handle(@PathVariable Long id, @RequestParam String remark) {
BehaviorRecord record = new BehaviorRecord();
record.setId(id);
record.setHandled(1);
record.setHandleRemark(remark);
record.setHandleTime(java.time.LocalDateTime.now());
behaviorRecordService.updateById(record);
return Result.ok("处理成功");
}
@Operation(summary = "获取预警列表")
@GetMapping("/warnings")
public Result<Page<BehaviorRecord>> warnings(
@RequestParam(defaultValue = "1") Long current,
@RequestParam(defaultValue = "10") Long size) {
LambdaQueryWrapper<BehaviorRecord> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(BehaviorRecord::getIsWarning, 1);
wrapper.orderByDesc(BehaviorRecord::getBehaviorTime);
return Result.success(behaviorRecordService.page(new Page<>(current, size), wrapper));
}
}

@ -0,0 +1,100 @@
package com.attendance.controller;
import com.attendance.common.Result;
import com.attendance.entity.AttTask;
import com.attendance.entity.Classroom;
import com.attendance.entity.Device;
import com.attendance.service.AttTaskService;
import com.attendance.service.ClassroomService;
import com.attendance.service.DeviceService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.*;
@Tag(name = "数据展示大屏", description = "大屏数据展示接口")
@RestController
@RequestMapping("/bigscreen")
public class BigScreenController {
private final AttTaskService attTaskService;
private final ClassroomService classroomService;
private final DeviceService deviceService;
public BigScreenController(AttTaskService attTaskService, ClassroomService classroomService, DeviceService deviceService) {
this.attTaskService = attTaskService;
this.classroomService = classroomService;
this.deviceService = deviceService;
}
@Operation(summary = "获取大屏核心统计数据")
@GetMapping("/stats")
public Result<Map<String, Object>> getStats() {
List<AttTask> todayTasks = attTaskService.getTasksByDate(LocalDate.now());
int totalShould = todayTasks.stream().mapToInt(AttTask::getTotalCount).sum();
int totalActual = todayTasks.stream().mapToInt(AttTask::getActualCount).sum();
BigDecimal attendanceRate = totalShould > 0
? BigDecimal.valueOf(totalActual * 100.0 / totalShould).setScale(1, RoundingMode.HALF_UP)
: BigDecimal.ZERO;
Map<String, Object> stats = new HashMap<>();
stats.put("attendanceRate", attendanceRate);
stats.put("focusRate", 85.2);
stats.put("trend", 2.5);
stats.put("focusTrend", 0.8);
return Result.success(stats);
}
@Operation(summary = "出勤趋势数据")
@GetMapping("/trend")
public Result<Map<String, Object>> getTrend() {
Map<String, Object> data = new HashMap<>();
data.put("dates", Arrays.asList("5/26", "5/27", "5/28", "5/29", "5/30", "5/31", "6/01"));
data.put("rates", Arrays.asList(93, 95, 94.5, 96, 95.8, 97, 96.8));
return Result.success(data);
}
@Operation(summary = "行为分布数据")
@GetMapping("/behavior-distribution")
public Result<List<Map<String, Object>>> getBehaviorDistribution() {
List<Map<String, Object>> list = new ArrayList<>();
list.add(createMap("name", "专注听讲", "value", 65, "color", "#52c41a"));
list.add(createMap("name", "举手互动", "value", 15, "color", "#1890ff"));
list.add(createMap("name", "低头书写", "value", 12, "color", "#722ed1"));
list.add(createMap("name", "交谈讨论", "value", 5, "color", "#faad14"));
list.add(createMap("name", "其他", "value", 3, "color", "#bfbfbf"));
return Result.success(list);
}
private Map<String, Object> createMap(Object... kv) {
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < kv.length; i += 2) {
map.put((String) kv[i], kv[i + 1]);
}
return map;
}
@Operation(summary = "获取教室监控列表")
@GetMapping("/cameras")
public Result<List<Classroom>> getCameras() {
return Result.success(classroomService.list(new LambdaQueryWrapper<Classroom>().eq(Classroom::getStatus, 1)));
}
@Operation(summary = "获取设备在线状态")
@GetMapping("/device-status")
public Result<Map<String, Long>> getDeviceStatus() {
long online = deviceService.count(new LambdaQueryWrapper<Device>().eq(Device::getOnlineStatus, 1));
long offline = deviceService.count(new LambdaQueryWrapper<Device>().eq(Device::getOnlineStatus, 0));
Map<String, Long> map = new HashMap<>();
map.put("online", online);
map.put("offline", offline);
return Result.success(map);
}
}

@ -0,0 +1,75 @@
package com.attendance.controller;
import com.attendance.annotation.OperationLog;
import com.attendance.common.Result;
import com.attendance.entity.Building;
import com.attendance.service.BuildingService;
import com.attendance.vo.BuildingVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "教学楼管理", description = "教学管理-教学楼相关接口")
@RestController
@RequestMapping("/building")
public class BuildingController {
private final BuildingService buildingService;
public BuildingController(BuildingService buildingService) {
this.buildingService = buildingService;
}
@Operation(summary = "分页查询教学楼列表")
@GetMapping("/page")
public Result<IPage<BuildingVO>> page(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current,
@Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size,
@Parameter(description = "关键词(教学楼名称/编码)") @RequestParam(required = false) String keyword) {
Page<BuildingVO> page = new Page<>(current, size);
return Result.success(buildingService.getBuildingPage(page, keyword));
}
@Operation(summary = "获取教学楼详情")
@GetMapping("/{id}")
public Result<Building> getById(@PathVariable Long id) {
return Result.success(buildingService.getById(id));
}
@OperationLog(module = "教学楼管理", action = "新增教学楼")
@Operation(summary = "新增教学楼")
@PostMapping
public Result<Void> save(@RequestBody Building building) {
buildingService.save(building);
return Result.ok("新增成功");
}
@OperationLog(module = "教学楼管理", action = "修改教学楼")
@Operation(summary = "修改教学楼")
@PutMapping("/{id}")
public Result<Void> update(@PathVariable Long id, @RequestBody Building building) {
building.setId(id);
buildingService.updateById(building);
return Result.ok("修改成功");
}
@OperationLog(module = "教学楼管理", action = "删除教学楼")
@Operation(summary = "删除教学楼")
@DeleteMapping
public Result<Void> remove(@Parameter(description = "教学楼ID列表") @RequestBody List<Long> ids) {
buildingService.removeByIds(ids);
return Result.ok("删除成功");
}
@Operation(summary = "获取全部教学楼")
@GetMapping("/list")
public Result<List<Building>> list() {
return Result.success(buildingService.list(new LambdaQueryWrapper<Building>().eq(Building::getStatus, 1)));
}
}

@ -0,0 +1,90 @@
package com.attendance.controller;
import com.attendance.annotation.OperationLog;
import com.attendance.common.Result;
import com.attendance.entity.Classroom;
import com.attendance.service.ClassroomService;
import com.attendance.vo.ClassroomAttendanceVO;
import com.attendance.vo.ClassroomVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.List;
@Tag(name = "教室管理", description = "教室信息相关接口")
@RestController
@RequestMapping("/classroom")
public class ClassroomController {
private final ClassroomService classroomService;
public ClassroomController(ClassroomService classroomService) {
this.classroomService = classroomService;
}
@Operation(summary = "分页查询教室列表")
@GetMapping("/page")
public Result<IPage<ClassroomVO>> page(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current,
@Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size,
@Parameter(description = "教学楼ID") @RequestParam(required = false) Long buildingId) {
Page<ClassroomVO> page = new Page<>(current, size);
return Result.success(classroomService.getClassroomPage(page, buildingId));
}
@Operation(summary = "获取教室详情")
@GetMapping("/{id}")
public Result<Classroom> getById(@PathVariable Long id) {
return Result.success(classroomService.getById(id));
}
@OperationLog(module = "教室管理", action = "新增教室")
@Operation(summary = "新增教室")
@PostMapping
public Result<Void> save(@RequestBody Classroom classroom) {
classroomService.save(classroom);
return Result.ok("新增成功");
}
@OperationLog(module = "教室管理", action = "修改教室")
@Operation(summary = "修改教室")
@PutMapping("/{id}")
public Result<Void> update(@PathVariable Long id, @RequestBody Classroom classroom) {
classroom.setId(id);
classroomService.updateById(classroom);
return Result.ok("修改成功");
}
@OperationLog(module = "教室管理", action = "删除教室")
@Operation(summary = "删除教室")
@DeleteMapping
public Result<Void> remove(@Parameter(description = "教室ID列表") @RequestBody List<Long> ids) {
classroomService.removeByIds(ids);
return Result.ok("删除成功");
}
@Operation(summary = "获取全部教室")
@GetMapping("/list")
public Result<List<ClassroomVO>> list(
@Parameter(description = "教学楼ID") @RequestParam(required = false) Long buildingId) {
return Result.success(classroomService.getClassroomList(buildingId));
}
@Operation(summary = "获取教室当前考勤信息")
@GetMapping("/{id}/current-attendance")
public Result<ClassroomAttendanceVO> currentAttendance(
@Parameter(description = "教室ID") @PathVariable Long id,
@Parameter(description = "当前时间格式yyyy-MM-dd HH:mm:ss") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime currentTime) {
if (currentTime == null) {
currentTime = LocalDateTime.now();
}
ClassroomAttendanceVO vo = classroomService.getCurrentAttendance(id, currentTime);
return Result.success(vo);
}
}

@ -0,0 +1,77 @@
package com.attendance.controller;
import com.attendance.annotation.OperationLog;
import com.attendance.common.Result;
import com.attendance.entity.Course;
import com.attendance.service.CourseService;
import com.attendance.vo.CourseVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "课程管理", description = "课程信息相关接口")
@RestController
@RequestMapping("/course")
public class CourseController {
private final CourseService courseService;
public CourseController(CourseService courseService) {
this.courseService = courseService;
}
@Operation(summary = "分页查询课程列表")
@GetMapping("/page")
public Result<IPage<CourseVO>> page(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current,
@Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size,
@Parameter(description = "课程名称") @RequestParam(required = false) String courseName,
@Parameter(description = "教师名称") @RequestParam(required = false) String teacherName,
@Parameter(description = "课程类型") @RequestParam(required = false) String courseType) {
Page<CourseVO> page = new Page<>(current, size);
return Result.success(courseService.getCoursePage(page, courseName, teacherName, courseType));
}
@Operation(summary = "获取课程详情")
@GetMapping("/{id}")
public Result<Course> getById(@PathVariable Long id) {
return Result.success(courseService.getById(id));
}
@OperationLog(module = "课程管理", action = "新增课程")
@Operation(summary = "新增课程")
@PostMapping
public Result<Void> save(@RequestBody Course course) {
courseService.save(course);
return Result.ok("新增成功");
}
@OperationLog(module = "课程管理", action = "修改课程")
@Operation(summary = "修改课程")
@PutMapping("/{id}")
public Result<Void> update(@PathVariable Long id, @RequestBody Course course) {
course.setId(id);
courseService.updateById(course);
return Result.ok("修改成功");
}
@OperationLog(module = "课程管理", action = "删除课程")
@Operation(summary = "删除课程")
@DeleteMapping
public Result<Void> remove(@Parameter(description = "课程ID列表") @RequestBody List<Long> ids) {
courseService.removeByIds(ids);
return Result.ok("删除成功");
}
@Operation(summary = "获取全部课程")
@GetMapping("/list")
public Result<List<Course>> list() {
return Result.success(courseService.list(new LambdaQueryWrapper<Course>().eq(Course::getStatus, 1)));
}
}

@ -0,0 +1,89 @@
package com.attendance.controller;
import com.attendance.annotation.OperationLog;
import com.attendance.common.Result;
import com.attendance.entity.CourseSchedule;
import com.attendance.service.CourseScheduleService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "课程安排", description = "课表/课程安排相关接口")
@RestController
@RequestMapping("/schedule")
public class CourseScheduleController {
private final CourseScheduleService courseScheduleService;
public CourseScheduleController(CourseScheduleService courseScheduleService) {
this.courseScheduleService = courseScheduleService;
}
@Operation(summary = "分页查询课程安排")
@GetMapping("/page")
public Result<Page<CourseSchedule>> page(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current,
@Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size,
@Parameter(description = "学期") @RequestParam(required = false) String semester,
@Parameter(description = "班级ID") @RequestParam(required = false) Long classId,
@Parameter(description = "教师ID") @RequestParam(required = false) Long teacherId) {
LambdaQueryWrapper<CourseSchedule> wrapper = new LambdaQueryWrapper<>();
if (semester != null && !semester.isEmpty()) {
wrapper.eq(CourseSchedule::getSemester, semester);
}
if (teacherId != null) {
wrapper.eq(CourseSchedule::getTeacherId, teacherId);
}
if (classId != null) {
wrapper.eq(CourseSchedule::getClassId, classId);
}
wrapper.orderByAsc(CourseSchedule::getWeekDay).orderByAsc(CourseSchedule::getStartSection);
return Result.success(courseScheduleService.page(new Page<>(current, size), wrapper));
}
@Operation(summary = "获取课程安排详情")
@GetMapping("/{id}")
public Result<CourseSchedule> getById(@PathVariable Long id) {
return Result.success(courseScheduleService.getById(id));
}
@OperationLog(module = "课程安排", action = "新增课程安排")
@Operation(summary = "新增课程安排")
@PostMapping
public Result<Void> save(@RequestBody CourseSchedule schedule) {
courseScheduleService.saveWithTasks(schedule);
return Result.ok("新增成功");
}
@OperationLog(module = "课程安排", action = "修改课程安排")
@Operation(summary = "修改课程安排")
@PutMapping("/{id}")
public Result<Void> update(@PathVariable Long id, @RequestBody CourseSchedule schedule) {
schedule.setId(id);
courseScheduleService.updateWithTasks(schedule);
return Result.ok("修改成功");
}
@OperationLog(module = "课程安排", action = "删除课程安排")
@Operation(summary = "删除课程安排")
@DeleteMapping
public Result<Void> remove(@Parameter(description = "课程安排ID列表") @RequestBody List<Long> ids) {
courseScheduleService.removeWithTasks(ids);
return Result.ok("删除成功");
}
@Operation(summary = "获取全部课程安排")
@GetMapping("/list")
public Result<List<CourseSchedule>> list(@RequestParam(required = false) String semester) {
LambdaQueryWrapper<CourseSchedule> wrapper = new LambdaQueryWrapper<>();
if (semester != null && !semester.isEmpty()) {
wrapper.eq(CourseSchedule::getSemester, semester);
}
return Result.success(courseScheduleService.list(wrapper));
}
}

@ -0,0 +1,71 @@
package com.attendance.controller;
import com.attendance.common.Result;
import com.attendance.entity.AttTask;
import com.attendance.service.AttTaskService;
import com.attendance.vo.AttTrendVO;
import com.attendance.vo.ClassRankVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.*;
@Tag(name = "首页仪表盘", description = "Dashboard 统计数据接口")
@RestController
@RequestMapping("/dashboard")
public class DashboardController {
private final AttTaskService attTaskService;
public DashboardController(AttTaskService attTaskService) {
this.attTaskService = attTaskService;
}
@Operation(summary = "获取今日核心统计数据")
@GetMapping("/stats")
public Result<Map<String, Object>> getStats() {
List<AttTask> todayTasks = attTaskService.getTasksByDate(LocalDate.now());
int totalShould = todayTasks.stream().mapToInt(AttTask::getTotalCount).sum();
int totalActual = todayTasks.stream().mapToInt(AttTask::getActualCount).sum();
BigDecimal attendanceRate = totalShould > 0
? BigDecimal.valueOf(totalActual * 100.0 / totalShould).setScale(1, RoundingMode.HALF_UP)
: BigDecimal.ZERO;
Map<String, Object> stats = new HashMap<>();
stats.put("attendanceRate", attendanceRate);
stats.put("classroomUsage", 78.5);
stats.put("warningCount", 3);
stats.put("taskCount", todayTasks.size());
return Result.success(stats);
}
@Operation(summary = "获取今日考勤任务列表")
@GetMapping("/tasks")
public Result<List<AttTask>> getTodayTasks() {
return Result.success(attTaskService.getTasksByDate(LocalDate.now()));
}
@Operation(summary = "出勤趋势")
@GetMapping("/trend")
public Result<List<AttTrendVO>> getTrend(
@Parameter(description = "统计天数") @RequestParam(defaultValue = "7") Integer days) {
return Result.success(attTaskService.getAttTrend(days));
}
@Operation(summary = "班级出勤排名")
@GetMapping("/ranking")
public Result<IPage<ClassRankVO>> getClassRanking(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current,
@Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size) {
Page<ClassRankVO> page = new Page<>(current, size);
return Result.success(attTaskService.getClassRankPage(page));
}
}

@ -0,0 +1,102 @@
package com.attendance.controller;
import com.attendance.annotation.OperationLog;
import com.attendance.common.Result;
import com.attendance.entity.Device;
import com.attendance.service.DeviceService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.List;
@Tag(name = "设备管理", description = "考勤设备管理接口")
@RestController
@RequestMapping("/device")
public class DeviceController {
private final DeviceService deviceService;
public DeviceController(DeviceService deviceService) {
this.deviceService = deviceService;
}
@Operation(summary = "分页查询设备列表")
@GetMapping("/page")
public Result<Page<Device>> page(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current,
@Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size,
@Parameter(description = "设备类型") @RequestParam(required = false) String deviceType,
@Parameter(description = "设备名称") @RequestParam(required = false) String deviceName,
@Parameter(description = "安装教室ID") @RequestParam(required = false) Long classroomId,
@Parameter(description = "在线状态") @RequestParam(required = false) Integer onlineStatus) {
LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<>();
if (deviceType != null && !deviceType.isEmpty()) {
wrapper.eq(Device::getDeviceType, deviceType);
}
if (onlineStatus != null) {
wrapper.eq(Device::getOnlineStatus, onlineStatus);
}
if (classroomId != null) {
wrapper.eq(Device::getClassroomId, classroomId);
}
if (deviceName != null) {
wrapper.like(Device::getDeviceName, deviceName);
}
wrapper.orderByDesc(Device::getCreatedAt);
return Result.success(deviceService.page(new Page<>(current, size), wrapper));
}
@Operation(summary = "获取设备详情")
@GetMapping("/{id}")
public Result<Device> getById(@PathVariable Long id) {
return Result.success(deviceService.getById(id));
}
@OperationLog(module = "设备管理", action = "新增设备")
@Operation(summary = "新增设备")
@PostMapping
public Result<Void> save(@RequestBody Device device) {
deviceService.save(device);
return Result.ok("新增成功");
}
@OperationLog(module = "设备管理", action = "修改设备")
@Operation(summary = "修改设备")
@PutMapping("/{id}")
public Result<Void> update(@PathVariable Long id, @RequestBody Device device) {
device.setId(id);
deviceService.updateById(device);
return Result.ok("修改成功");
}
@OperationLog(module = "设备管理", action = "删除设备")
@Operation(summary = "删除设备")
@DeleteMapping
public Result<Void> remove(@Parameter(description = "设备ID列表") @RequestBody List<Long> ids) {
deviceService.removeByIds(ids);
return Result.ok("删除成功");
}
@OperationLog(module = "设备管理", action = "设备心跳上报")
@Operation(summary = "设备心跳上报")
@PostMapping("/{id}/heartbeat")
public Result<Void> heartbeat(@PathVariable Long id) {
Device device = new Device();
device.setId(id);
device.setLastHeartbeat(LocalDateTime.now());
device.setOnlineStatus(1);
deviceService.updateById(device);
return Result.success();
}
@Operation(summary = "获取全部设备")
@GetMapping("/list")
public Result<List<Device>> list() {
return Result.success(deviceService.list());
}
}

@ -0,0 +1,84 @@
package com.attendance.controller;
import com.attendance.annotation.OperationLog;
import com.attendance.common.Result;
import com.attendance.entity.EduClass;
import com.attendance.service.EduClassService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "班级管理", description = "教学管理-班级相关接口")
@RestController
@RequestMapping("/class")
public class EduClassController {
private final EduClassService eduClassService;
public EduClassController(EduClassService eduClassService) {
this.eduClassService = eduClassService;
}
@Operation(summary = "分页查询班级列表")
@GetMapping("/page")
public Result<Page<EduClass>> page(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current,
@Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size,
@Parameter(description = "关键词(班级名称/班级编码/专业)") @RequestParam(required = false) String keyword,
@Parameter(description = "年级") @RequestParam(required = false) String grade) {
LambdaQueryWrapper<EduClass> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(EduClass::getDeleted, 0);
if (keyword != null && !keyword.isEmpty()) {
wrapper.and(w -> w.like(EduClass::getClassName, keyword)
.or().like(EduClass::getClassCode, keyword)
.or().like(EduClass::getMajor, keyword));
}
if (grade != null && !grade.isEmpty()) {
wrapper.eq(EduClass::getGrade, grade);
}
wrapper.orderByDesc(EduClass::getCreatedAt);
return Result.success(eduClassService.page(new Page<>(current, size), wrapper));
}
@Operation(summary = "获取班级详情")
@GetMapping("/{id}")
public Result<EduClass> getById(@PathVariable Long id) {
return Result.success(eduClassService.getById(id));
}
@OperationLog(module = "班级管理", action = "新增班级")
@Operation(summary = "新增班级")
@PostMapping
public Result<Void> save(@RequestBody EduClass eduClass) {
eduClassService.save(eduClass);
return Result.ok("新增成功");
}
@OperationLog(module = "班级管理", action = "修改班级")
@Operation(summary = "修改班级")
@PutMapping("/{id}")
public Result<Void> update(@PathVariable Long id, @RequestBody EduClass eduClass) {
eduClass.setId(id);
eduClassService.updateById(eduClass);
return Result.ok("修改成功");
}
@OperationLog(module = "班级管理", action = "删除班级")
@Operation(summary = "删除班级")
@DeleteMapping
public Result<Void> remove(@Parameter(description = "班级ID列表") @RequestBody List<Long> ids) {
eduClassService.removeByIds(ids);
return Result.ok("删除成功");
}
@Operation(summary = "获取全部班级")
@GetMapping("/list")
public Result<List<EduClass>> list() {
return Result.success(eduClassService.list(new LambdaQueryWrapper<EduClass>().eq(EduClass::getStatus, 1)));
}
}

@ -0,0 +1,74 @@
package com.attendance.controller;
import com.attendance.annotation.OperationLog;
import com.attendance.common.Result;
import com.attendance.entity.SysPermission;
import com.attendance.service.SysPermissionService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "权限管理", description = "权限管理-菜单/权限相关接口")
@RestController
@RequestMapping("/permission")
public class PermissionController {
private final SysPermissionService sysPermissionService;
public PermissionController(SysPermissionService sysPermissionService) {
this.sysPermissionService = sysPermissionService;
}
@Operation(summary = "查询全部权限列表")
@GetMapping("/list")
public Result<List<SysPermission>> list() {
LambdaQueryWrapper<SysPermission> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysPermission::getStatus, 1);
wrapper.orderByAsc(SysPermission::getSortOrder);
return Result.success(sysPermissionService.list(wrapper));
}
@Operation(summary = "获取权限树")
@GetMapping("/tree")
public Result<List<SysPermission>> tree() {
LambdaQueryWrapper<SysPermission> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysPermission::getStatus, 1);
wrapper.orderByAsc(SysPermission::getSortOrder);
return Result.success(sysPermissionService.list(wrapper));
}
@Operation(summary = "获取权限详情")
@GetMapping("/{id}")
public Result<SysPermission> getById(@PathVariable Long id) {
return Result.success(sysPermissionService.getById(id));
}
@OperationLog(module = "权限管理", action = "新增权限")
@Operation(summary = "新增权限")
@PostMapping
public Result<Void> save(@RequestBody SysPermission permission) {
sysPermissionService.save(permission);
return Result.ok("新增成功");
}
@OperationLog(module = "权限管理", action = "修改权限")
@Operation(summary = "修改权限")
@PutMapping("/{id}")
public Result<Void> update(@PathVariable Long id, @RequestBody SysPermission permission) {
permission.setId(id);
sysPermissionService.updateById(permission);
return Result.ok("修改成功");
}
@OperationLog(module = "权限管理", action = "删除权限")
@Operation(summary = "删除权限")
@DeleteMapping
public Result<Void> remove(@Parameter(description = "权限ID列表") @RequestBody List<Long> ids) {
sysPermissionService.removeByIds(ids);
return Result.ok("删除成功");
}
}

@ -0,0 +1,89 @@
package com.attendance.controller;
import com.attendance.annotation.OperationLog;
import com.attendance.common.Result;
import com.attendance.entity.SysRole;
import com.attendance.service.SysPermissionService;
import com.attendance.service.SysRoleService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "角色管理", description = "权限管理-角色相关接口")
@RestController
@RequestMapping("/role")
public class RoleController {
private final SysRoleService sysRoleService;
private final SysPermissionService sysPermissionService;
public RoleController(SysRoleService sysRoleService, SysPermissionService sysPermissionService) {
this.sysRoleService = sysRoleService;
this.sysPermissionService = sysPermissionService;
}
@Operation(summary = "分页查询角色列表")
@GetMapping("/page")
public Result<Page<SysRole>> page(
@RequestParam(defaultValue = "1") Long current,
@RequestParam(defaultValue = "10") Long size) {
LambdaQueryWrapper<SysRole> wrapper = new LambdaQueryWrapper<>();
wrapper.orderByAsc(SysRole::getSortOrder);
return Result.success(sysRoleService.page(new Page<>(current, size), wrapper));
}
@Operation(summary = "获取角色详情")
@GetMapping("/{id}")
public Result<SysRole> getById(@PathVariable Long id) {
return Result.success(sysRoleService.getById(id));
}
@OperationLog(module = "角色管理", action = "新增角色")
@Operation(summary = "新增角色")
@PostMapping
public Result<Void> save(@RequestBody SysRole role) {
sysRoleService.save(role);
return Result.ok("新增成功");
}
@OperationLog(module = "角色管理", action = "修改角色")
@Operation(summary = "修改角色")
@PutMapping("/{id}")
public Result<Void> update(@PathVariable Long id, @RequestBody SysRole role) {
role.setId(id);
sysRoleService.updateById(role);
return Result.ok("修改成功");
}
@OperationLog(module = "角色管理", action = "删除角色")
@Operation(summary = "删除角色")
@DeleteMapping
public Result<Void> remove(@Parameter(description = "角色ID列表") @RequestBody List<Long> ids) {
sysRoleService.removeByIds(ids);
return Result.ok("删除成功");
}
@Operation(summary = "获取角色权限列表")
@GetMapping("/{roleId}/permissions")
public Result<List<com.attendance.entity.SysPermission>> getRolePermissions(@PathVariable Long roleId) {
return Result.success(sysPermissionService.getPermissionsByRoleId(roleId));
}
@OperationLog(module = "角色管理", action = "分配角色权限")
@Operation(summary = "分配角色权限")
@PostMapping("/{roleId}/permissions")
public Result<Void> assignPermissions(@PathVariable Long roleId, @RequestBody List<Long> permissionIds) {
return Result.ok("分配成功");
}
@Operation(summary = "获取全部角色")
@GetMapping("/list")
public Result<List<SysRole>> list() {
return Result.success(sysRoleService.list());
}
}

@ -0,0 +1,92 @@
package com.attendance.controller;
import cn.hutool.http.HttpUtil;
import com.attendance.annotation.OperationLog;
import com.attendance.common.Result;
import com.attendance.dto.StudentDTO;
import com.attendance.entity.Student;
import com.attendance.service.StudentService;
import com.attendance.vo.StudentVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "学生管理", description = "人员管理-学生相关接口")
@RestController
@RequestMapping("/student")
@Slf4j
public class StudentController {
@Value("${stream.import-face-url}")
private String importFaceUrl;
private final StudentService studentService;
public StudentController(StudentService studentService) {
this.studentService = studentService;
}
@Operation(summary = "分页查询学生列表")
@GetMapping("/page")
public Result<IPage<StudentVO>> page(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current,
@Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size,
@Parameter(description = "所属班级ID") @RequestParam(required = false) Long classId,
@Parameter(description = "关键词(学号/姓名)") @RequestParam(required = false) String keyword) {
Page<StudentVO> page = new Page<>(current, size);
return Result.success(studentService.getStudentPage(page, classId, keyword));
}
@Operation(summary = "获取学生详情")
@GetMapping("/{id}")
public Result<Student> getById(@PathVariable Long id) {
return Result.success(studentService.getById(id));
}
@OperationLog(module = "学生管理", action = "新增学生")
@Operation(summary = "新增学生")
@PostMapping(consumes = "multipart/form-data")
public Result<Void> save(StudentDTO dto) throws Exception {
studentService.addStudentWithFaceSamples(dto);
// 成功后调用外部接口导入人脸样本
try {
String importResult = HttpUtil.post(importFaceUrl, "");
log.info("import-face-samples response: {}", importResult);
} catch (Exception ex) {
log.warn("调用import-face-samples接口失败: {}", ex.getMessage());
}
return Result.ok("新增成功");
}
@OperationLog(module = "学生管理", action = "修改学生")
@Operation(summary = "修改学生")
@PutMapping("/{id}")
public Result<Void> update(@PathVariable Long id, @RequestBody Student student) {
student.setId(id);
studentService.updateById(student);
return Result.ok("修改成功");
}
@OperationLog(module = "学生管理", action = "删除学生")
@Operation(summary = "删除学生")
@DeleteMapping
public Result<Void> remove(@Parameter(description = "学生ID列表") @RequestBody List<Long> ids) {
studentService.removeStudentsWithFaceData(ids);
return Result.ok("删除成功");
}
@Operation(summary = "获取全部学生")
@GetMapping("/list")
public Result<List<Student>> list() {
return Result.success(studentService.list(new LambdaQueryWrapper<Student>().eq(Student::getStatus, 1)));
}
}

@ -0,0 +1,92 @@
package com.attendance.controller;
import com.attendance.annotation.OperationLog;
import com.attendance.common.Result;
import com.attendance.dto.PasswordDTO;
import com.attendance.entity.SysUser;
import com.attendance.service.SysUserService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
@Tag(name = "用户管理", description = "系统用户管理接口")
@RestController
@RequestMapping("/user")
public class SysUserController {
private final SysUserService sysUserService;
public SysUserController(SysUserService sysUserService) {
this.sysUserService = sysUserService;
}
@Operation(summary = "分页查询用户列表")
@GetMapping("/page")
public Result<Page<SysUser>> page(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current,
@Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size,
@Parameter(description = "关键词(用户名/真实姓名)") @RequestParam(required = false) String keyword) {
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
if (keyword != null && !keyword.isEmpty()) {
wrapper.and(w -> w.like(SysUser::getUsername, keyword).or().like(SysUser::getRealName, keyword));
}
wrapper.orderByDesc(SysUser::getCreatedAt);
return Result.success(sysUserService.page(new Page<>(current, size), wrapper));
}
@Operation(summary = "获取用户详情")
@GetMapping("/{id}")
public Result<SysUser> getById(@PathVariable Long id) {
return Result.success(sysUserService.getById(id));
}
@OperationLog(module = "用户管理", action = "新增用户")
@Operation(summary = "新增用户")
@PostMapping
public Result<Void> save(@RequestBody SysUser user) {
if (user.getPassword() != null && !user.getPassword().isEmpty()) {
user.setPassword(DigestUtils.md5DigestAsHex(user.getPassword().getBytes(StandardCharsets.UTF_8)));
}
sysUserService.save(user);
return Result.ok("新增成功");
}
@OperationLog(module = "用户管理", action = "修改用户")
@Operation(summary = "修改用户")
@PutMapping("/{id}")
public Result<Void> update(@PathVariable Long id, @RequestBody SysUser user) {
user.setId(id);
user.setPassword(null);
sysUserService.updateById(user);
return Result.ok("修改成功");
}
@OperationLog(module = "用户管理", action = "删除用户")
@Operation(summary = "删除用户")
@DeleteMapping
public Result<Void> remove(@Parameter(description = "用户ID列表") @RequestBody List<Long> ids) {
sysUserService.removeByIds(ids);
return Result.ok("删除成功");
}
@OperationLog(module = "用户管理", action = "修改密码")
@Operation(summary = "修改密码")
@PutMapping("/{id}/password")
public Result<Void> updatePassword(@PathVariable Long id, @RequestBody PasswordDTO passwordDTO) {
sysUserService.updatePassword(id, passwordDTO);
return Result.ok("密码修改成功");
}
@Operation(summary = "获取全部用户")
@GetMapping("/list")
public Result<List<SysUser>> list() {
return Result.success(sysUserService.list());
}
}

@ -0,0 +1,84 @@
package com.attendance.controller;
import com.attendance.annotation.OperationLog;
import com.attendance.common.Result;
import com.attendance.entity.Teacher;
import com.attendance.service.TeacherService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "教师管理", description = "人员管理-教师相关接口")
@RestController
@RequestMapping("/teacher")
public class TeacherController {
private final TeacherService teacherService;
public TeacherController(TeacherService teacherService) {
this.teacherService = teacherService;
}
@Operation(summary = "分页查询教师列表")
@GetMapping("/page")
public Result<Page<Teacher>> page(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current,
@Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size,
@Parameter(description = "关键词(工号/姓名/院系)") @RequestParam(required = false) String keyword,
@Parameter(description = "职称") @RequestParam(required = false) String title) {
LambdaQueryWrapper<Teacher> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Teacher::getDeleted, 0);
if (keyword != null && !keyword.isEmpty()) {
wrapper.and(w -> w.like(Teacher::getTeacherNo, keyword)
.or().like(Teacher::getName, keyword)
.or().like(Teacher::getDepartment, keyword));
}
if (title != null && !title.isEmpty()) {
wrapper.eq(Teacher::getTitle, title);
}
wrapper.orderByDesc(Teacher::getCreatedAt);
return Result.success(teacherService.page(new Page<>(current, size), wrapper));
}
@Operation(summary = "获取教师详情")
@GetMapping("/{id}")
public Result<Teacher> getById(@PathVariable Long id) {
return Result.success(teacherService.getById(id));
}
@OperationLog(module = "教师管理", action = "新增教师")
@Operation(summary = "新增教师")
@PostMapping
public Result<Void> save(@RequestBody Teacher teacher) {
teacherService.save(teacher);
return Result.ok("新增成功");
}
@OperationLog(module = "教师管理", action = "修改教师")
@Operation(summary = "修改教师")
@PutMapping("/{id}")
public Result<Void> update(@PathVariable Long id, @RequestBody Teacher teacher) {
teacher.setId(id);
teacherService.updateById(teacher);
return Result.ok("修改成功");
}
@OperationLog(module = "教师管理", action = "删除教师")
@Operation(summary = "删除教师")
@DeleteMapping
public Result<Void> remove(@Parameter(description = "教师ID列表") @RequestBody List<Long> ids) {
teacherService.removeByIds(ids);
return Result.ok("删除成功");
}
@Operation(summary = "获取全部教师")
@GetMapping("/list")
public Result<List<Teacher>> list() {
return Result.success(teacherService.list(new LambdaQueryWrapper<Teacher>().eq(Teacher::getStatus, 1)));
}
}

@ -0,0 +1,19 @@
package com.attendance.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
@Schema(description = "登录请求参数")
public class LoginDTO {
@NotBlank(message = "请输入工号/学号")
@Schema(description = "登录账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "admin")
private String username;
@NotBlank(message = "请输入密码")
@Schema(description = "登录密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
private String password;
}

@ -0,0 +1,36 @@
package com.attendance.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "登录响应结果")
public class LoginResultDTO {
@Schema(description = "访问令牌")
private String token;
@Schema(description = "token类型")
private String tokenType;
@Schema(description = "用户ID")
private Long userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "真实姓名")
private String realName;
@Schema(description = "头像")
private String avatar;
@Schema(description = "角色编码")
private String role;
@Schema(description = "角色名称")
private String roleName;
@Schema(description = "所属学校")
private String schoolName;
}

@ -0,0 +1,15 @@
package com.attendance.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "分页查询参数")
public class PageDTO {
@Schema(description = "当前页码", example = "1")
private Long current = 1L;
@Schema(description = "每页条数", example = "10")
private Long size = 10L;
}

@ -0,0 +1,19 @@
package com.attendance.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
@Schema(description = "修改密码参数")
public class PasswordDTO {
@NotBlank(message = "请输入原密码")
@Schema(description = "原密码", requiredMode = Schema.RequiredMode.REQUIRED)
private String oldPassword;
@NotBlank(message = "请输入新密码")
@Schema(description = "新密码", requiredMode = Schema.RequiredMode.REQUIRED)
private String newPassword;
}

@ -0,0 +1,51 @@
package com.attendance.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.NotBlank;
import java.util.List;
@Data
@Schema(description = "新增学生请求参数")
public class StudentDTO {
@NotBlank(message = "学号不能为空")
@Schema(description = "学号", requiredMode = Schema.RequiredMode.REQUIRED)
private String studentNo;
@NotBlank(message = "姓名不能为空")
@Schema(description = "姓名", requiredMode = Schema.RequiredMode.REQUIRED)
private String name;
@Schema(description = "性别0-女 1-男")
private Integer gender;
@Schema(description = "头像URL")
private String avatar;
@Schema(description = "所属班级ID")
private Long classId;
@Schema(description = "所属学校ID")
private Long schoolId;
@Schema(description = "手机号")
private String phone;
@Schema(description = "邮箱")
private String email;
@Schema(description = "关联系统用户ID")
private Long userId;
@Schema(description = "正脸样本图片数组,可传多张")
private List<MultipartFile> frontImage;
@Schema(description = "左脸样本图片数组,可传多张")
private List<MultipartFile> leftImage;
@Schema(description = "右脸样本图片数组,可传多张")
private List<MultipartFile> rightImage;
}

@ -0,0 +1,74 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("att_detail")
@Schema(description = "考勤明细实体")
public class AttDetail extends BaseEntity {
@Schema(description = "考勤任务ID")
private Long taskId;
@Schema(description = "课程安排ID")
private Long scheduleId;
@Schema(description = "考勤记录ID")
private Long recordId;
@Schema(description = "学生ID")
private Long studentId;
@Schema(description = "学号")
private String studentNo;
@Schema(description = "学生姓名")
private String studentName;
@Schema(description = "课程ID")
private Long courseId;
@Schema(description = "教室ID")
private Long classroomId;
@Schema(description = "考勤日期")
private LocalDate attDate;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "签到时间")
private LocalDateTime checkInTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "签退时间")
private LocalDateTime checkOutTime;
@Schema(description = "考勤状态0-未签到 1-正常 2-迟到 3-缺勤 4-早退 5-请假")
private Integer attStatus;
@Schema(description = "签到方式")
private Integer checkType;
@Schema(description = "人脸相似度")
private BigDecimal faceSimilarity;
@Schema(description = "抓拍人脸图片URL")
private String faceImage;
@Schema(description = "签到设备ID")
private Long deviceId;
@Schema(description = "备注")
private String remark;
@Schema(description = "所属学校ID")
private Long schoolId;
}

@ -0,0 +1,77 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("att_record")
@Schema(description = "考勤记录汇总实体")
public class AttRecord extends BaseEntity {
@Schema(description = "考勤任务ID")
private Long taskId;
@Schema(description = "课程安排ID")
private Long scheduleId;
@Schema(description = "课程ID")
private Long courseId;
@Schema(description = "教室ID")
private Long classroomId;
@Schema(description = "班级ID")
private Long classId;
@Schema(description = "教师ID")
private Long teacherId;
@Schema(description = "考勤日期")
private LocalDate attDate;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "开始时间")
private LocalDateTime startTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "结束时间")
private LocalDateTime endTime;
@Schema(description = "应到人数")
private Integer totalCount;
@Schema(description = "实到人数")
private Integer actualCount;
@Schema(description = "缺勤人数")
private Integer absentCount;
@Schema(description = "迟到人数")
private Integer lateCount;
@Schema(description = "早退人数")
private Integer leaveEarlyCount;
@Schema(description = "出勤率%")
private BigDecimal attendanceRate;
@Schema(description = "缺勤率%")
private BigDecimal absentRate;
@Schema(description = "记录状态")
private Integer recordStatus;
@Schema(description = "备注")
private String remark;
@Schema(description = "所属学校ID")
private Long schoolId;
}

@ -0,0 +1,54 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalTime;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("att_rule")
@Schema(description = "考勤规则实体")
public class AttRule extends BaseEntity {
@Schema(description = "规则名称")
private String ruleName;
@Schema(description = "规则类型")
private String ruleType;
@Schema(description = "关联目标ID")
private Long targetId;
@JsonFormat(pattern = "HH:mm:ss")
@Schema(description = "签到开始时间")
private LocalTime checkInStart;
@JsonFormat(pattern = "HH:mm:ss")
@Schema(description = "签到截止时间")
private LocalTime checkInEnd;
@Schema(description = "迟到阈值(分钟)")
private Integer lateThreshold;
@Schema(description = "缺勤阈值(分钟)")
private Integer absentThreshold;
@Schema(description = "早退阈值(分钟)")
private Integer earlyLeaveThreshold;
@Schema(description = "是否允许补签")
private Integer allowMakeUp;
@Schema(description = "补签时限(小时)")
private Integer makeUpLimit;
@Schema(description = "所属学校ID")
private Long schoolId;
@Schema(description = "状态")
private Integer status;
}

@ -0,0 +1,80 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("att_task")
@Schema(description = "考勤任务实体")
public class AttTask extends BaseEntity {
@Schema(description = "任务编号")
private String taskNo;
@Schema(description = "课程安排ID")
private Long scheduleId;
@Schema(description = "课程ID")
private Long courseId;
@Schema(description = "课程名称")
private String courseName;
@Schema(description = "教室ID")
private Long classroomId;
@Schema(description = "教室名称")
private String classroomName;
@Schema(description = "教师ID")
private Long teacherId;
@Schema(description = "教师姓名")
private String teacherName;
@Schema(description = "班级ID")
private Long classId;
@Schema(description = "考勤日期")
private LocalDate attDate;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "上课开始时间")
private LocalDateTime startTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "上课结束时间")
private LocalDateTime endTime;
@Schema(description = "应到人数")
private Integer totalCount;
@Schema(description = "实到人数")
private Integer actualCount;
@Schema(description = "缺勤人数")
private Integer absentCount;
@Schema(description = "迟到人数")
private Integer lateCount;
@Schema(description = "早退人数")
private Integer leaveEarlyCount;
@Schema(description = "出勤率%")
private BigDecimal attendanceRate;
@Schema(description = "任务状态")
private Integer taskStatus;
@Schema(description = "所属学校ID")
private Long schoolId;
}

@ -0,0 +1,35 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class BaseEntity {
@TableId(type = IdType.AUTO)
@Schema(description = "主键ID")
private Long id;
@TableLogic
@TableField(fill = FieldFill.INSERT)
@Schema(description = "是否删除0-未删除 1-已删除")
private Integer deleted;
@TableField(fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@TableField(fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新时间")
private LocalDateTime updatedAt;
}

@ -0,0 +1,67 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("behavior_record")
@Schema(description = "课堂行为记录实体")
public class BehaviorRecord extends BaseEntity {
@Schema(description = "考勤任务ID")
private Long taskId;
@Schema(description = "课程ID")
private Long courseId;
@Schema(description = "教室ID")
private Long classroomId;
@Schema(description = "学生ID")
private Long studentId;
@Schema(description = "行为类型ID")
private Long behaviorTypeId;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "行为发生时间")
private LocalDateTime behaviorTime;
@Schema(description = "持续时间(秒)")
private Integer duration;
@Schema(description = "AI识别置信度")
private BigDecimal confidence;
@Schema(description = "行为抓拍图片")
private String snapshotUrl;
@Schema(description = "是否预警")
private Integer isWarning;
@Schema(description = "预警级别")
private Integer warningLevel;
@Schema(description = "是否已处理")
private Integer handled;
@Schema(description = "处理人ID")
private Long handlerId;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "处理时间")
private LocalDateTime handleTime;
@Schema(description = "处理备注")
private String handleRemark;
@Schema(description = "所属学校ID")
private Long schoolId;
}

@ -0,0 +1,39 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("behavior_type")
@Schema(description = "行为类型实体")
public class BehaviorType extends BaseEntity {
@Schema(description = "类型编码")
private String typeCode;
@Schema(description = "类型名称")
private String typeName;
@Schema(description = "分类")
private String category;
@Schema(description = "图表颜色")
private String color;
@Schema(description = "排序")
private Integer sortOrder;
@Schema(description = "描述")
private String description;
@Schema(description = "状态")
private Integer status;
@TableField(exist = false)
@Schema(description = "该类型的统计次数")
private Integer count;
}

@ -0,0 +1,28 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("edu_building")
@Schema(description = "教学楼实体")
public class Building extends BaseEntity {
@Schema(description = "教学楼名称")
private String buildingName;
@Schema(description = "教学楼编码")
private String buildingCode;
@Schema(description = "楼层数")
private Integer floors;
@Schema(description = "所属学校ID")
private Long schoolId;
@Schema(description = "状态0-停用 1-正常")
private Integer status;
}

@ -0,0 +1,40 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("edu_classroom")
@Schema(description = "教室实体")
public class Classroom extends BaseEntity {
@Schema(description = "教室编号")
private String roomNo;
@Schema(description = "教室名称")
private String roomName;
@Schema(description = "所属教学楼ID")
private Long buildingId;
@Schema(description = "楼层")
private Integer floor;
@Schema(description = "容纳人数")
private Integer capacity;
@Schema(description = "教室类型")
private String roomType;
@Schema(description = "关联摄像头设备ID")
private Long cameraDeviceId;
@Schema(description = "所属学校ID")
private Long schoolId;
@Schema(description = "状态0-停用 1-正常")
private Integer status;
}

@ -0,0 +1,39 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("edu_course")
@Schema(description = "课程实体")
public class Course extends BaseEntity {
@Schema(description = "课程编码")
private String courseCode;
@Schema(description = "课程名称")
private String courseName;
@Schema(description = "课程类型")
private String courseType;
@Schema(description = "学分")
private BigDecimal credit;
@Schema(description = "授课教师ID")
private Long teacherId;
@Schema(description = "所属学校ID")
private Long schoolId;
@Schema(description = "课程描述")
private String description;
@Schema(description = "状态")
private Integer status;
}

@ -0,0 +1,56 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("edu_course_schedule")
@Schema(description = "课程安排实体")
public class CourseSchedule extends BaseEntity {
@Schema(description = "课程ID")
private Long courseId;
@Schema(description = "教学楼ID")
private Long buildingId;
@Schema(description = "教室ID")
private Long classroomId;
@Schema(description = "上课班级ID")
private Long classId;
@Schema(description = "授课教师ID")
private Long teacherId;
@Schema(description = "星期几1-7")
private Integer weekDay;
@Schema(description = "开始节次")
private Integer startSection;
@Schema(description = "结束节次")
private Integer endSection;
@Schema(description = "开始周")
private Integer startWeek;
@Schema(description = "结束周")
private Integer endWeek;
@Schema(description = "学期")
private String semester;
@Schema(description = "首次上课时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
private LocalDateTime firstClassTime;
@Schema(description = "状态")
private Integer status;
}

@ -0,0 +1,71 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("device")
@Schema(description = "设备实体")
public class Device extends BaseEntity {
@Schema(description = "设备编号")
private String deviceNo;
@Schema(description = "设备名称")
private String deviceName;
@Schema(description = "设备类型")
private String deviceType;
@Schema(description = "品牌")
private String brand;
@Schema(description = "型号")
private String model;
@Schema(description = "序列号")
private String serialNo;
@Schema(description = "IP地址")
private String ipAddress;
@Schema(description = "端口")
private Integer port;
@Schema(description = "MAC地址")
private String macAddress;
@Schema(description = "安装教室ID")
private Long classroomId;
@Schema(description = "安装位置描述")
private String location;
@Schema(description = "固件版本")
private String firmwareVersion;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "最后心跳时间")
private LocalDateTime lastHeartbeat;
@Schema(description = "在线状态")
private Integer onlineStatus;
@Schema(description = "使用状态")
private Integer status;
@Schema(description = "所属学校ID")
private Long schoolId;
@Schema(description = "流类型")
private String streamType;
@Schema(description = "流地址")
private String streamUrl;
}

@ -0,0 +1,37 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("edu_class")
@Schema(description = "班级实体")
public class EduClass extends BaseEntity {
@Schema(description = "班级名称")
private String className;
@Schema(description = "班级编码")
private String classCode;
@Schema(description = "年级")
private String grade;
@Schema(description = "专业")
private String major;
@Schema(description = "所属学校ID")
private Long schoolId;
@Schema(description = "学生人数")
private Integer studentCount;
@Schema(description = "班主任ID")
private Long headteacherId;
@Schema(description = "状态")
private Integer status;
}

@ -0,0 +1,48 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("face_identities")
@Schema(description = "人脸特征实体")
public class FaceIdentity {
@TableId(type = IdType.AUTO)
@Schema(description = "主键ID")
private Long id;
@Schema(description = "姓名")
private String name;
@Schema(description = "角度")
private String angle;
@Schema(description = "特征向量JSON")
private String embeddingJson;
@Schema(description = "是否启用0-禁用 1-启用")
private Integer enabled;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@Schema(description = "来源样本ID")
private Long sourceSampleId;
@Schema(description = "来源图片路径")
private String sourceImagePath;
@Schema(description = "关联学生ID")
private Integer studentId;
@Schema(description = "逻辑删除0-未删除 1-已删除")
@TableLogic
private Integer deleted;
}

@ -0,0 +1,36 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("face_samples")
@Schema(description = "人脸样本实体")
public class FaceSample {
@TableId(type = IdType.AUTO)
@Schema(description = "主键ID")
private Integer id;
@Schema(description = "关联的学生ID")
private Integer studentId;
@Schema(description = "样本类型front-正脸 left-左脸 right-右脸")
private String sampleType;
@Schema(description = "图片存储路径")
private String imagePath;
@Schema(description = "上传时间")
private LocalDateTime uploadTime;
@Schema(description = "逻辑删除0-未删除 1-已删除")
@TableLogic
private Integer deleted;
}

@ -0,0 +1,49 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("monitor_record")
@Schema(description = "教室监控记录实体")
public class MonitorRecord extends BaseEntity {
@Schema(description = "教室ID")
private Long classroomId;
@Schema(description = "监控设备ID")
private Long deviceId;
@Schema(description = "记录日期")
private LocalDate recordDate;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "开始时间")
private LocalDateTime startTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "结束时间")
private LocalDateTime endTime;
@Schema(description = "直播流地址")
private String streamUrl;
@Schema(description = "录像地址")
private String recordUrl;
@Schema(description = "录像文件大小(字节)")
private Long fileSize;
@Schema(description = "记录状态")
private Integer recordStatus;
@Schema(description = "所属学校ID")
private Long schoolId;
}

@ -0,0 +1,49 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("edu_student")
@Schema(description = "学生实体")
public class Student extends BaseEntity {
@Schema(description = "学号")
private String studentNo;
@Schema(description = "姓名")
private String name;
@Schema(description = "性别0-女 1-男")
private Integer gender;
@Schema(description = "头像URL")
private String avatar;
@Schema(description = "人脸特征值")
private String faceFeature;
@Schema(description = "人脸照片URL")
private String faceImage;
@Schema(description = "所属班级ID")
private Long classId;
@Schema(description = "所属学校ID")
private Long schoolId;
@Schema(description = "手机号")
private String phone;
@Schema(description = "邮箱")
private String email;
@Schema(description = "关联系统用户ID")
private Long userId;
@Schema(description = "状态0-离校 1-在读")
private Integer status;
}

@ -0,0 +1,61 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_operation_log")
@Schema(description = "系统操作日志实体")
public class SysOperationLog extends BaseEntity {
@Schema(description = "操作用户ID")
private Long userId;
@Schema(description = "操作用户名")
private String username;
@Schema(description = "操作模块")
private String module;
@Schema(description = "操作动作")
private String action;
@Schema(description = "请求方法")
private String method;
@Schema(description = "请求URL")
private String requestUrl;
@Schema(description = "请求参数")
private String requestParams;
@Schema(description = "响应数据")
private String responseData;
@Schema(description = "IP地址")
private String ipAddress;
@Schema(description = "User-Agent")
private String userAgent;
@Schema(description = "执行时长(毫秒)")
private Integer duration;
@Schema(description = "状态0-失败 1-成功")
private Integer status;
@Schema(description = "错误信息")
private String errorMsg;
/** 表中无 updated_at 字段,忽略基类映射 */
@TableField(exist = false)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updatedAt;
}

@ -0,0 +1,37 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_permission")
@Schema(description = "权限实体")
public class SysPermission extends BaseEntity {
@Schema(description = "权限编码")
private String permCode;
@Schema(description = "权限名称")
private String permName;
@Schema(description = "权限类型1-菜单 2-按钮 3-接口")
private Integer permType;
@Schema(description = "父权限ID")
private Long parentId;
@Schema(description = "前端路由路径")
private String path;
@Schema(description = "图标")
private String icon;
@Schema(description = "排序")
private Integer sortOrder;
@Schema(description = "状态")
private Integer status;
}

@ -0,0 +1,28 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_role")
@Schema(description = "角色实体")
public class SysRole extends BaseEntity {
@Schema(description = "角色编码")
private String roleCode;
@Schema(description = "角色名称")
private String roleName;
@Schema(description = "角色描述")
private String description;
@Schema(description = "排序")
private Integer sortOrder;
@Schema(description = "状态0-禁用 1-启用")
private Integer status;
}

@ -0,0 +1,30 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("sys_role_permission")
@Schema(description = "角色权限关联实体")
public class SysRolePermission {
@Schema(description = "主键ID")
private Long id;
@Schema(description = "角色ID")
private Long roleId;
@Schema(description = "权限ID")
private Long permissionId;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@Schema(description = "逻辑删除0-未删除 1-已删除")
@TableLogic
private Integer deleted;
}

@ -0,0 +1,28 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_school")
@Schema(description = "学校/机构实体")
public class SysSchool extends BaseEntity {
@Schema(description = "学校名称")
private String name;
@Schema(description = "学校编码")
private String code;
@Schema(description = "地址")
private String address;
@Schema(description = "联系电话")
private String contactPhone;
@Schema(description = "状态0-禁用 1-启用")
private Integer status;
}

@ -0,0 +1,50 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_user")
@Schema(description = "系统用户实体")
public class SysUser extends BaseEntity {
@Schema(description = "登录账号")
private String username;
@JsonIgnore
@Schema(description = "登录密码")
private String password;
@Schema(description = "真实姓名")
private String realName;
@Schema(description = "头像URL")
private String avatar;
@Schema(description = "角色ID")
private Long roleId;
@Schema(description = "所属学校ID")
private Long schoolId;
@Schema(description = "手机号")
private String phone;
@Schema(description = "邮箱")
private String email;
@Schema(description = "最后登录时间")
private LocalDateTime lastLoginTime;
@Schema(description = "最后登录IP")
private String lastLoginIp;
@Schema(description = "状态0-禁用 1-启用")
private Integer status;
}

@ -0,0 +1,46 @@
package com.attendance.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("edu_teacher")
@Schema(description = "教师实体")
public class Teacher extends BaseEntity {
@Schema(description = "工号")
private String teacherNo;
@Schema(description = "姓名")
private String name;
@Schema(description = "性别0-女 1-男")
private Integer gender;
@Schema(description = "头像URL")
private String avatar;
@Schema(description = "职称")
private String title;
@Schema(description = "所属院系")
private String department;
@Schema(description = "所属学校ID")
private Long schoolId;
@Schema(description = "手机号")
private String phone;
@Schema(description = "邮箱")
private String email;
@Schema(description = "关联系统用户ID")
private Long userId;
@Schema(description = "状态")
private Integer status;
}

@ -0,0 +1,16 @@
package com.attendance.mapper;
import com.attendance.entity.AttDetail;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface AttDetailMapper extends BaseMapper<AttDetail> {
@Select("SELECT * FROM att_detail WHERE task_id = #{taskId} AND deleted = 0 ORDER BY id")
List<AttDetail> selectByTaskId(@Param("taskId") Long taskId);
}

@ -0,0 +1,17 @@
package com.attendance.mapper;
import com.attendance.entity.AttRecord;
import com.attendance.vo.AttRecordVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface AttRecordMapper extends BaseMapper<AttRecord> {
IPage<AttRecordVO> selectHistoryPage(Page<AttRecordVO> page,
@Param("courseName") String courseName,
@Param("attDate") String attDate);
}

@ -0,0 +1,9 @@
package com.attendance.mapper;
import com.attendance.entity.AttRule;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AttRuleMapper extends BaseMapper<AttRule> {
}

@ -0,0 +1,31 @@
package com.attendance.mapper;
import com.attendance.entity.AttTask;
import com.attendance.vo.AttTaskVO;
import com.attendance.vo.AttTrendVO;
import com.attendance.vo.ClassRankVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDate;
import java.util.List;
@Mapper
public interface AttTaskMapper extends BaseMapper<AttTask> {
@Select("SELECT * FROM att_task WHERE att_date = #{date} AND deleted = 0 ORDER BY start_time DESC")
List<AttTask> selectByDate(@Param("date") LocalDate date);
IPage<AttTaskVO> selectTaskPage(Page<AttTaskVO> page,
@Param("attDate") LocalDate attDate,
@Param("keyword") String keyword,
@Param("taskStatus") Integer taskStatus);
IPage<ClassRankVO> selectClassRankPage(Page<ClassRankVO> page);
List<AttTrendVO> selectAttTrend(@Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
}

@ -0,0 +1,81 @@
package com.attendance.mapper;
import com.attendance.vo.BehaviorRecordVO;
import com.attendance.entity.BehaviorRecord;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
@Mapper
public interface BehaviorRecordMapper extends BaseMapper<BehaviorRecord> {
@Select("SELECT bt.type_name as name, COUNT(*) as value " +
"FROM behavior_record br " +
"INNER JOIN behavior_type bt ON br.behavior_type_id = bt.id " +
"WHERE br.task_id = #{taskId} AND br.deleted = 0 " +
"GROUP BY br.behavior_type_id, bt.type_name")
List<Map<String, Object>> selectBehaviorStatsByTaskId(@Param("taskId") Long taskId);
@Select("<script>" +
"SELECT bt.type_name as name, COUNT(*) as value " +
"FROM behavior_record br " +
"INNER JOIN behavior_type bt ON br.behavior_type_id = bt.id " +
"INNER JOIN att_task at2 ON br.task_id = at2.id " +
"WHERE br.deleted = 0 " +
"<if test='courseId != null'> AND at2.course_id = #{courseId} </if>" +
"<if test='teacherId != null'> AND at2.teacher_id = #{teacherId} </if>" +
"<if test='attDate != null'> AND at2.att_date = #{attDate} </if>" +
"GROUP BY br.behavior_type_id, bt.type_name" +
"</script>")
List<Map<String, Object>> selectBehaviorStatsWithFilters(
@Param("courseId") Long courseId,
@Param("teacherId") Long teacherId,
@Param("attDate") LocalDate attDate);
@Select("<script>" +
"SELECT CONCAT(LPAD(t.slot * 2, 2, '0'), ':00-', LPAD(t.slot * 2 + 2, 2, '0'), ':00') as timeSlot, " +
"t.typeName, t.color, COUNT(*) as value " +
"FROM (" +
"SELECT FLOOR(HOUR(br.behavior_time) / 2) as slot, bt.type_name as typeName, bt.color " +
"FROM behavior_record br " +
"INNER JOIN behavior_type bt ON br.behavior_type_id = bt.id " +
"INNER JOIN att_task at2 ON br.task_id = at2.id " +
"WHERE br.deleted = 0 AND HOUR(br.behavior_time) &gt;= 8 AND HOUR(br.behavior_time) &lt; 22 " +
"<if test='courseId != null'> AND at2.course_id = #{courseId} </if>" +
"<if test='teacherId != null'> AND at2.teacher_id = #{teacherId} </if>" +
"<if test='attDate != null'> AND at2.att_date = #{attDate} </if>" +
") t " +
"GROUP BY t.slot, t.typeName, t.color " +
"ORDER BY t.slot, t.typeName" +
"</script>")
List<Map<String, Object>> selectTimePeriodStats(
@Param("courseId") Long courseId,
@Param("teacherId") Long teacherId,
@Param("attDate") LocalDate attDate);
@Select("<script>" +
"SELECT br.*, at2.course_name, at2.teacher_name, bt.type_name as behaviorTypeName, es.name as studentName " +
"FROM behavior_record br " +
"INNER JOIN att_task at2 ON br.task_id = at2.id " +
"INNER JOIN behavior_type bt ON br.behavior_type_id = bt.id " +
"LEFT JOIN edu_student es ON br.student_id = es.id " +
"WHERE br.deleted = 0 " +
"<if test='courseId != null'> AND at2.course_id = #{courseId} </if>" +
"<if test='teacherId != null'> AND at2.teacher_id = #{teacherId} </if>" +
"<if test='attDate != null'> AND at2.att_date = #{attDate} </if>" +
"<if test='behaviorTypeId != null'> AND br.behavior_type_id = #{behaviorTypeId} </if>" +
"ORDER BY br.behavior_time DESC" +
"</script>")
IPage<BehaviorRecordVO> selectBehaviorRecordPage(
IPage<?> page,
@Param("courseId") Long courseId,
@Param("teacherId") Long teacherId,
@Param("attDate") LocalDate attDate,
@Param("behaviorTypeId") Long behaviorTypeId);
}

@ -0,0 +1,9 @@
package com.attendance.mapper;
import com.attendance.entity.BehaviorType;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BehaviorTypeMapper extends BaseMapper<BehaviorType> {
}

@ -0,0 +1,16 @@
package com.attendance.mapper;
import com.attendance.entity.Building;
import com.attendance.vo.BuildingVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface BuildingMapper extends BaseMapper<Building> {
IPage<BuildingVO> selectBuildingPage(Page<BuildingVO> page,
@Param("keyword") String keyword);
}

@ -0,0 +1,20 @@
package com.attendance.mapper;
import com.attendance.entity.Classroom;
import com.attendance.vo.ClassroomVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface ClassroomMapper extends BaseMapper<Classroom> {
IPage<ClassroomVO> selectClassroomPage(Page<ClassroomVO> page,
@Param("buildingId") Long buildingId);
List<ClassroomVO> selectClassroomList(@Param("buildingId") Long buildingId);
}

@ -0,0 +1,18 @@
package com.attendance.mapper;
import com.attendance.entity.Course;
import com.attendance.vo.CourseVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface CourseMapper extends BaseMapper<Course> {
IPage<CourseVO> selectCoursePage(Page<CourseVO> page,
@Param("courseName") String courseName,
@Param("teacherName") String teacherName,
@Param("courseType") String courseType);
}

@ -0,0 +1,9 @@
package com.attendance.mapper;
import com.attendance.entity.CourseSchedule;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CourseScheduleMapper extends BaseMapper<CourseSchedule> {
}

@ -0,0 +1,9 @@
package com.attendance.mapper;
import com.attendance.entity.Device;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface DeviceMapper extends BaseMapper<Device> {
}

@ -0,0 +1,9 @@
package com.attendance.mapper;
import com.attendance.entity.EduClass;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EduClassMapper extends BaseMapper<EduClass> {
}

@ -0,0 +1,9 @@
package com.attendance.mapper;
import com.attendance.entity.FaceIdentity;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface FaceIdentityMapper extends BaseMapper<FaceIdentity> {
}

@ -0,0 +1,9 @@
package com.attendance.mapper;
import com.attendance.entity.FaceSample;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface FaceSampleMapper extends BaseMapper<FaceSample> {
}

@ -0,0 +1,9 @@
package com.attendance.mapper;
import com.attendance.entity.MonitorRecord;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MonitorRecordMapper extends BaseMapper<MonitorRecord> {
}

@ -0,0 +1,17 @@
package com.attendance.mapper;
import com.attendance.entity.Student;
import com.attendance.vo.StudentVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface StudentMapper extends BaseMapper<Student> {
IPage<StudentVO> selectStudentPage(Page<StudentVO> page,
@Param("classId") Long classId,
@Param("keyword") String keyword);
}

@ -0,0 +1,9 @@
package com.attendance.mapper;
import com.attendance.entity.SysOperationLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysOperationLogMapper extends BaseMapper<SysOperationLog> {
}

@ -0,0 +1,18 @@
package com.attendance.mapper;
import com.attendance.entity.SysPermission;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface SysPermissionMapper extends BaseMapper<SysPermission> {
@Select("SELECT p.* FROM sys_permission p " +
"INNER JOIN sys_role_permission rp ON p.id = rp.permission_id " +
"WHERE rp.role_id = #{roleId} AND p.status = 1 ORDER BY p.sort_order")
List<SysPermission> selectByRoleId(@Param("roleId") Long roleId);
}

@ -0,0 +1,9 @@
package com.attendance.mapper;
import com.attendance.entity.SysRole;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysRoleMapper extends BaseMapper<SysRole> {
}

@ -0,0 +1,9 @@
package com.attendance.mapper;
import com.attendance.entity.SysRolePermission;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysRolePermissionMapper extends BaseMapper<SysRolePermission> {
}

@ -0,0 +1,9 @@
package com.attendance.mapper;
import com.attendance.entity.SysSchool;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysSchoolMapper extends BaseMapper<SysSchool> {
}

@ -0,0 +1,13 @@
package com.attendance.mapper;
import com.attendance.entity.SysUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
@Select("SELECT * FROM sys_user WHERE username = #{username} AND deleted = 0 LIMIT 1")
SysUser selectByUsername(String username);
}

@ -0,0 +1,9 @@
package com.attendance.mapper;
import com.attendance.entity.Teacher;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface TeacherMapper extends BaseMapper<Teacher> {
}

@ -0,0 +1,11 @@
package com.attendance.service;
import com.attendance.entity.AttDetail;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface AttDetailService extends IService<AttDetail> {
List<AttDetail> getDetailsByTaskId(Long taskId);
}

@ -0,0 +1,14 @@
package com.attendance.service;
import com.attendance.entity.AttRecord;
import com.attendance.entity.Student;
import com.attendance.vo.AttRecordVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface AttRecordService extends IService<AttRecord> {
IPage<AttRecordVO> getHistoryPage(IPage<AttRecordVO> page, String courseName, String attDate);
}

@ -0,0 +1,7 @@
package com.attendance.service;
import com.attendance.entity.AttRule;
import com.baomidou.mybatisplus.extension.service.IService;
public interface AttRuleService extends IService<AttRule> {
}

@ -0,0 +1,23 @@
package com.attendance.service;
import com.attendance.entity.AttTask;
import com.attendance.vo.AttTaskVO;
import com.attendance.vo.AttTrendVO;
import com.attendance.vo.ClassRankVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import java.time.LocalDate;
import java.util.List;
public interface AttTaskService extends IService<AttTask> {
List<AttTask> getTasksByDate(LocalDate date);
IPage<AttTaskVO> getTaskPage(Page<AttTaskVO> page, LocalDate attDate, String keyword, Integer taskStatus);
IPage<ClassRankVO> getClassRankPage(Page<ClassRankVO> page);
List<AttTrendVO> getAttTrend(Integer days);
}

@ -0,0 +1,21 @@
package com.attendance.service;
import com.attendance.vo.BehaviorRecordVO;
import com.attendance.entity.BehaviorRecord;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
public interface BehaviorRecordService extends IService<BehaviorRecord> {
List<Map<String, Object>> getBehaviorStatsByTaskId(Long taskId);
List<Map<String, Object>> getBehaviorStatsWithFilters(Long courseId, Long teacherId, LocalDate attDate);
List<Map<String, Object>> getTimePeriodStats(Long courseId, Long teacherId, LocalDate attDate);
IPage<BehaviorRecordVO> getBehaviorRecordPage(IPage<?> page, Long courseId, Long teacherId, LocalDate attDate, Long behaviorTypeId);
}

@ -0,0 +1,7 @@
package com.attendance.service;
import com.attendance.entity.BehaviorType;
import com.baomidou.mybatisplus.extension.service.IService;
public interface BehaviorTypeService extends IService<BehaviorType> {
}

@ -0,0 +1,17 @@
package com.attendance.service;
import com.attendance.entity.Building;
import com.attendance.vo.BuildingVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public interface BuildingService extends IService<Building> {
IPage<BuildingVO> getBuildingPage(IPage<BuildingVO> page, String keyword);
@Transactional(rollbackFor = Exception.class)
boolean removeByIds(List<Long> ids);
}

@ -0,0 +1,23 @@
package com.attendance.service;
import com.attendance.entity.Classroom;
import com.attendance.vo.ClassroomAttendanceVO;
import com.attendance.vo.ClassroomVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
public interface ClassroomService extends IService<Classroom> {
IPage<ClassroomVO> getClassroomPage(IPage<ClassroomVO> page, Long buildingId);
List<ClassroomVO> getClassroomList(Long buildingId);
ClassroomAttendanceVO getCurrentAttendance(Long classroomId, LocalDateTime currentTime);
@Transactional(rollbackFor = Exception.class)
boolean removeByIds(List<Long> ids);
}

@ -0,0 +1,15 @@
package com.attendance.service;
import com.attendance.entity.CourseSchedule;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface CourseScheduleService extends IService<CourseSchedule> {
void saveWithTasks(CourseSchedule schedule);
void updateWithTasks(CourseSchedule schedule);
void removeWithTasks(List<Long> ids);
}

@ -0,0 +1,11 @@
package com.attendance.service;
import com.attendance.entity.Course;
import com.attendance.vo.CourseVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
public interface CourseService extends IService<Course> {
IPage<CourseVO> getCoursePage(IPage<CourseVO> page, String courseName, String teacherName, String courseType);
}

@ -0,0 +1,7 @@
package com.attendance.service;
import com.attendance.entity.Device;
import com.baomidou.mybatisplus.extension.service.IService;
public interface DeviceService extends IService<Device> {
}

@ -0,0 +1,7 @@
package com.attendance.service;
import com.attendance.entity.EduClass;
import com.baomidou.mybatisplus.extension.service.IService;
public interface EduClassService extends IService<EduClass> {
}

@ -0,0 +1,7 @@
package com.attendance.service;
import com.attendance.entity.FaceIdentity;
import com.baomidou.mybatisplus.extension.service.IService;
public interface FaceIdentityService extends IService<FaceIdentity> {
}

@ -0,0 +1,9 @@
package com.attendance.service;
import com.attendance.entity.FaceSample;
import com.baomidou.mybatisplus.extension.service.IService;
public interface FaceSampleService extends IService<FaceSample> {
void addFaceSample(FaceSample faceSample);
}

@ -0,0 +1,7 @@
package com.attendance.service;
import com.attendance.entity.MonitorRecord;
import com.baomidou.mybatisplus.extension.service.IService;
public interface MonitorRecordService extends IService<MonitorRecord> {
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save