diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af62a96 --- /dev/null +++ b/.gitignore @@ -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/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..27ef4be --- /dev/null +++ b/pom.xml @@ -0,0 +1,126 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.18 + + + + com.attendance + attendance-system-server + 1.0.0 + jar + attendance-system-server + 教室智能人脸考勤系统后端服务 + + + 1.8 + 3.5.5 + 4.4.0 + 4.4.0 + 2.0.49 + 8.5.7 + + + + + + + cn.hutool + hutool-all + 5.8.16 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-aop + + + + + mysql + mysql-connector-java + 8.0.33 + runtime + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + + + com.github.xiaoymin + knife4j-openapi3-spring-boot-starter + ${knife4j.version} + + + + + com.auth0 + java-jwt + ${jwt.version} + + + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson2.version} + + + + + io.minio + minio + ${minio.version} + + + + + org.projectlombok + lombok + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + diff --git a/sql/attendance_system.sql b/sql/attendance_system.sql new file mode 100644 index 0000000..4eb4c48 --- /dev/null +++ b/sql/attendance_system.sql @@ -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; diff --git a/src/main/java/com/attendance/AttendanceApplication.java b/src/main/java/com/attendance/AttendanceApplication.java new file mode 100644 index 0000000..0d31f45 --- /dev/null +++ b/src/main/java/com/attendance/AttendanceApplication.java @@ -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); + } +} diff --git a/src/main/java/com/attendance/annotation/OperationLog.java b/src/main/java/com/attendance/annotation/OperationLog.java new file mode 100644 index 0000000..2b56d2f --- /dev/null +++ b/src/main/java/com/attendance/annotation/OperationLog.java @@ -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 ""; +} diff --git a/src/main/java/com/attendance/aspect/OperationLogAspect.java b/src/main/java/com/attendance/aspect/OperationLogAspect.java new file mode 100644 index 0000000..b32d64b --- /dev/null +++ b/src/main/java/com/attendance/aspect/OperationLogAspect.java @@ -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; + } +} diff --git a/src/main/java/com/attendance/common/BusinessException.java b/src/main/java/com/attendance/common/BusinessException.java new file mode 100644 index 0000000..24d7d2c --- /dev/null +++ b/src/main/java/com/attendance/common/BusinessException.java @@ -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; + } +} diff --git a/src/main/java/com/attendance/common/GlobalExceptionHandler.java b/src/main/java/com/attendance/common/GlobalExceptionHandler.java new file mode 100644 index 0000000..8de8b10 --- /dev/null +++ b/src/main/java/com/attendance/common/GlobalExceptionHandler.java @@ -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 handleBusinessException(BusinessException e) { + log.warn("业务异常: {}", e.getMessage()); + return Result.error(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(BindException.class) + public Result 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 handleIllegalArgumentException(IllegalArgumentException e) { + log.warn("参数异常: {}", e.getMessage()); + return Result.error(ResultCode.PARAM_ERROR.getCode(), e.getMessage()); + } + + @ExceptionHandler(Exception.class) + public Result handleException(Exception e) { + log.error("系统异常", e); + return Result.error("服务器异常,请稍后重试"); + } +} diff --git a/src/main/java/com/attendance/common/Result.java b/src/main/java/com/attendance/common/Result.java new file mode 100644 index 0000000..2ebdb93 --- /dev/null +++ b/src/main/java/com/attendance/common/Result.java @@ -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 { + + @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 Result success() { + return success(null); + } + + public static Result success(T data) { + Result result = new Result<>(); + result.setCode(ResultCode.SUCCESS.getCode()); + result.setMessage(ResultCode.SUCCESS.getMessage()); + result.setData(data); + return result; + } + + public static Result success(String message, T data) { + Result result = new Result<>(); + result.setCode(ResultCode.SUCCESS.getCode()); + result.setMessage(message); + result.setData(data); + return result; + } + + public static Result ok(String message) { + Result result = new Result<>(); + result.setCode(ResultCode.SUCCESS.getCode()); + result.setMessage(message); + return result; + } + + public static Result error(String message) { + return error(ResultCode.ERROR.getCode(), message); + } + + public static Result error(ResultCode resultCode) { + return error(resultCode.getCode(), resultCode.getMessage()); + } + + public static Result error(Integer code, String message) { + Result result = new Result<>(); + result.setCode(code); + result.setMessage(message); + return result; + } +} diff --git a/src/main/java/com/attendance/common/ResultCode.java b/src/main/java/com/attendance/common/ResultCode.java new file mode 100644 index 0000000..12171d0 --- /dev/null +++ b/src/main/java/com/attendance/common/ResultCode.java @@ -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; + } +} diff --git a/src/main/java/com/attendance/common/UserContext.java b/src/main/java/com/attendance/common/UserContext.java new file mode 100644 index 0000000..c6929ed --- /dev/null +++ b/src/main/java/com/attendance/common/UserContext.java @@ -0,0 +1,45 @@ +package com.attendance.common; + +/** + * 用户上下文(ThreadLocal),用于在请求链路中传递当前用户信息 + */ +public class UserContext { + + private static final ThreadLocal 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; + } + } +} diff --git a/src/main/java/com/attendance/config/JwtInterceptor.java b/src/main/java/com/attendance/config/JwtInterceptor.java new file mode 100644 index 0000000..91c39bf --- /dev/null +++ b/src/main/java/com/attendance/config/JwtInterceptor.java @@ -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))); + } +} diff --git a/src/main/java/com/attendance/config/Knife4jConfig.java b/src/main/java/com/attendance/config/Knife4jConfig.java new file mode 100644 index 0000000..3ca3687 --- /dev/null +++ b/src/main/java/com/attendance/config/Knife4jConfig.java @@ -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"))); + } +} diff --git a/src/main/java/com/attendance/config/MyBatisPlusConfig.java b/src/main/java/com/attendance/config/MyBatisPlusConfig.java new file mode 100644 index 0000000..e429207 --- /dev/null +++ b/src/main/java/com/attendance/config/MyBatisPlusConfig.java @@ -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; + } +} diff --git a/src/main/java/com/attendance/config/MyMetaObjectHandler.java b/src/main/java/com/attendance/config/MyMetaObjectHandler.java new file mode 100644 index 0000000..5674571 --- /dev/null +++ b/src/main/java/com/attendance/config/MyMetaObjectHandler.java @@ -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()); + } +} diff --git a/src/main/java/com/attendance/config/WebMvcConfig.java b/src/main/java/com/attendance/config/WebMvcConfig.java new file mode 100644 index 0000000..d92e486 --- /dev/null +++ b/src/main/java/com/attendance/config/WebMvcConfig.java @@ -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" + ); + } +} diff --git a/src/main/java/com/attendance/controller/AttRuleController.java b/src/main/java/com/attendance/controller/AttRuleController.java new file mode 100644 index 0000000..5f2605d --- /dev/null +++ b/src/main/java/com/attendance/controller/AttRuleController.java @@ -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( + @RequestParam(defaultValue = "1") Long current, + @RequestParam(defaultValue = "10") Long size) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.orderByDesc(AttRule::getCreatedAt); + return Result.success(attRuleService.page(new Page<>(current, size), wrapper)); + } + + @Operation(summary = "获取规则详情") + @GetMapping("/{id}") + public Result getById(@PathVariable Long id) { + return Result.success(attRuleService.getById(id)); + } + + @OperationLog(module = "考勤规则", action = "新增考勤规则") + @Operation(summary = "新增考勤规则") + @PostMapping + public Result save(@RequestBody AttRule rule) { + attRuleService.save(rule); + return Result.ok("新增成功"); + } + + @OperationLog(module = "考勤规则", action = "修改考勤规则") + @Operation(summary = "修改考勤规则") + @PutMapping("/{id}") + public Result update(@PathVariable Long id, @RequestBody AttRule rule) { + rule.setId(id); + attRuleService.updateById(rule); + return Result.ok("修改成功"); + } + + @OperationLog(module = "考勤规则", action = "删除考勤规则") + @Operation(summary = "删除考勤规则") + @DeleteMapping + public Result remove(@Parameter(description = "规则ID列表") @RequestBody List ids) { + attRuleService.removeByIds(ids); + return Result.ok("删除成功"); + } + + @Operation(summary = "获取全部规则") + @GetMapping("/list") + public Result> list() { + return Result.success(attRuleService.list()); + } +} diff --git a/src/main/java/com/attendance/controller/AttendanceController.java b/src/main/java/com/attendance/controller/AttendanceController.java new file mode 100644 index 0000000..5ef8a50 --- /dev/null +++ b/src/main/java/com/attendance/controller/AttendanceController.java @@ -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> 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 page = new Page<>(current, size); + return Result.success(attTaskService.getTaskPage(page, attDate, keyword, taskStatus)); + } + + @Operation(summary = "获取考勤任务详情") + @GetMapping("/task/{id}") + public Result getTaskById(@PathVariable Long id) { + return Result.success(attTaskService.getById(id)); + } + + @OperationLog(module = "考勤管理", action = "新增考勤任务") + @Operation(summary = "新增考勤任务") + @PostMapping("/task") + public Result saveTask(@RequestBody AttTask task) { + attTaskService.save(task); + return Result.ok("新增成功"); + } + + @OperationLog(module = "考勤管理", action = "修改考勤任务") + @Operation(summary = "修改考勤任务") + @PutMapping("/task/{id}") + public Result 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 removeTask(@Parameter(description = "考勤任务ID列表") @RequestBody List ids) { + attTaskService.removeByIds(ids); + return Result.ok("删除成功"); + } + + @Operation(summary = "分页查询考勤记录汇总") + @GetMapping("/record/page") + public Result> recordPage( + @RequestParam(defaultValue = "1") Long current, + @RequestParam(defaultValue = "10") Long size, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate attDate) { + LambdaQueryWrapper 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 getRecordById(@PathVariable Long id) { + return Result.success(attRecordService.getById(id)); + } + + @Operation(summary = "根据任务ID查询考勤明细") + @GetMapping("/detail/{taskId}") + public Result> getDetailsByTaskId(@PathVariable Long taskId) { + return Result.success(attDetailService.getDetailsByTaskId(taskId)); + } + + @OperationLog(module = "考勤管理", action = "修改考勤明细") + @Operation(summary = "修改考勤明细") + @PutMapping("/detail/{id}") + public Result updateDetail(@PathVariable Long id, @RequestBody AttDetail detail) { + detail.setId(id); + attDetailService.updateById(detail); + return Result.ok("修改成功"); + } + + @Operation(summary = "分页查询考勤明细") + @GetMapping("/detail/page") + public Result> 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 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> 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 page = new Page<>(current, size); + return Result.success(attRecordService.getHistoryPage(page, courseName, attDate)); + } + +} diff --git a/src/main/java/com/attendance/controller/AuthController.java b/src/main/java/com/attendance/controller/AuthController.java new file mode 100644 index 0000000..e239ccc --- /dev/null +++ b/src/main/java/com/attendance/controller/AuthController.java @@ -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 login(@Valid @RequestBody LoginDTO loginDTO) { + return Result.success(sysUserService.login(loginDTO)); + } + + @Operation(summary = "用户登出") + @PostMapping("/logout") + public Result logout() { + return Result.success(); + } +} diff --git a/src/main/java/com/attendance/controller/BehaviorController.java b/src/main/java/com/attendance/controller/BehaviorController.java new file mode 100644 index 0000000..6a28c2c --- /dev/null +++ b/src/main/java/com/attendance/controller/BehaviorController.java @@ -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( + @RequestParam(defaultValue = "1") Long current, + @RequestParam(defaultValue = "10") Long size, + @RequestParam(required = false) Long taskId, + @RequestParam(required = false) Long behaviorTypeId) { + LambdaQueryWrapper 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> getTypes() { + return Result.success(behaviorTypeService.list( + new LambdaQueryWrapper().eq(BehaviorType::getStatus, 1).orderByAsc(BehaviorType::getSortOrder))); + } + + @Operation(summary = "获取行为类型列表(含统计次数,支持筛选)") + @GetMapping("/types-with-stats") + public Result> 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 types = behaviorTypeService.list( + new LambdaQueryWrapper().eq(BehaviorType::getStatus, 1).orderByAsc(BehaviorType::getSortOrder)); + List> stats = behaviorRecordService.getBehaviorStatsWithFilters(courseId, teacherId, attDate); + Map 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>> getStats(@PathVariable Long taskId) { + return Result.success(behaviorRecordService.getBehaviorStatsByTaskId(taskId)); + } + + @Operation(summary = "按时段统计行为次数(08:00-22:00,每2小时一个区间)") + @GetMapping("/stats/time-period") + public Result>> 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> 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 page = new Page<>(current, size); + return Result.success(behaviorRecordService.getBehaviorRecordPage(page, courseId, teacherId, attDate, behaviorTypeId)); + } + + @OperationLog(module = "课堂行为", action = "新增行为记录") + @Operation(summary = "新增行为记录") + @PostMapping + public Result save(@RequestBody BehaviorRecord record) { + behaviorRecordService.save(record); + return Result.ok("新增成功"); + } + + @OperationLog(module = "课堂行为", action = "标记行为已处理") + @Operation(summary = "标记行为已处理") + @PutMapping("/{id}/handle") + public Result 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> warnings( + @RequestParam(defaultValue = "1") Long current, + @RequestParam(defaultValue = "10") Long size) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(BehaviorRecord::getIsWarning, 1); + wrapper.orderByDesc(BehaviorRecord::getBehaviorTime); + return Result.success(behaviorRecordService.page(new Page<>(current, size), wrapper)); + } +} diff --git a/src/main/java/com/attendance/controller/BigScreenController.java b/src/main/java/com/attendance/controller/BigScreenController.java new file mode 100644 index 0000000..89e13b0 --- /dev/null +++ b/src/main/java/com/attendance/controller/BigScreenController.java @@ -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> getStats() { + List 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 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> getTrend() { + Map 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>> getBehaviorDistribution() { + List> 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 createMap(Object... kv) { + Map 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> getCameras() { + return Result.success(classroomService.list(new LambdaQueryWrapper().eq(Classroom::getStatus, 1))); + } + + @Operation(summary = "获取设备在线状态") + @GetMapping("/device-status") + public Result> getDeviceStatus() { + long online = deviceService.count(new LambdaQueryWrapper().eq(Device::getOnlineStatus, 1)); + long offline = deviceService.count(new LambdaQueryWrapper().eq(Device::getOnlineStatus, 0)); + Map map = new HashMap<>(); + map.put("online", online); + map.put("offline", offline); + return Result.success(map); + } +} diff --git a/src/main/java/com/attendance/controller/BuildingController.java b/src/main/java/com/attendance/controller/BuildingController.java new file mode 100644 index 0000000..e91d6ea --- /dev/null +++ b/src/main/java/com/attendance/controller/BuildingController.java @@ -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> page( + @Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current, + @Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size, + @Parameter(description = "关键词(教学楼名称/编码)") @RequestParam(required = false) String keyword) { + Page page = new Page<>(current, size); + return Result.success(buildingService.getBuildingPage(page, keyword)); + } + + @Operation(summary = "获取教学楼详情") + @GetMapping("/{id}") + public Result getById(@PathVariable Long id) { + return Result.success(buildingService.getById(id)); + } + + @OperationLog(module = "教学楼管理", action = "新增教学楼") + @Operation(summary = "新增教学楼") + @PostMapping + public Result save(@RequestBody Building building) { + buildingService.save(building); + return Result.ok("新增成功"); + } + + @OperationLog(module = "教学楼管理", action = "修改教学楼") + @Operation(summary = "修改教学楼") + @PutMapping("/{id}") + public Result update(@PathVariable Long id, @RequestBody Building building) { + building.setId(id); + buildingService.updateById(building); + return Result.ok("修改成功"); + } + + @OperationLog(module = "教学楼管理", action = "删除教学楼") + @Operation(summary = "删除教学楼") + @DeleteMapping + public Result remove(@Parameter(description = "教学楼ID列表") @RequestBody List ids) { + buildingService.removeByIds(ids); + return Result.ok("删除成功"); + } + + @Operation(summary = "获取全部教学楼") + @GetMapping("/list") + public Result> list() { + return Result.success(buildingService.list(new LambdaQueryWrapper().eq(Building::getStatus, 1))); + } +} diff --git a/src/main/java/com/attendance/controller/ClassroomController.java b/src/main/java/com/attendance/controller/ClassroomController.java new file mode 100644 index 0000000..6e9c4be --- /dev/null +++ b/src/main/java/com/attendance/controller/ClassroomController.java @@ -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> page( + @Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current, + @Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size, + @Parameter(description = "教学楼ID") @RequestParam(required = false) Long buildingId) { + Page page = new Page<>(current, size); + return Result.success(classroomService.getClassroomPage(page, buildingId)); + } + + @Operation(summary = "获取教室详情") + @GetMapping("/{id}") + public Result getById(@PathVariable Long id) { + return Result.success(classroomService.getById(id)); + } + + @OperationLog(module = "教室管理", action = "新增教室") + @Operation(summary = "新增教室") + @PostMapping + public Result save(@RequestBody Classroom classroom) { + classroomService.save(classroom); + return Result.ok("新增成功"); + } + + @OperationLog(module = "教室管理", action = "修改教室") + @Operation(summary = "修改教室") + @PutMapping("/{id}") + public Result update(@PathVariable Long id, @RequestBody Classroom classroom) { + classroom.setId(id); + classroomService.updateById(classroom); + return Result.ok("修改成功"); + } + + @OperationLog(module = "教室管理", action = "删除教室") + @Operation(summary = "删除教室") + @DeleteMapping + public Result remove(@Parameter(description = "教室ID列表") @RequestBody List ids) { + classroomService.removeByIds(ids); + return Result.ok("删除成功"); + } + + @Operation(summary = "获取全部教室") + @GetMapping("/list") + public Result> list( + @Parameter(description = "教学楼ID") @RequestParam(required = false) Long buildingId) { + return Result.success(classroomService.getClassroomList(buildingId)); + } + + @Operation(summary = "获取教室当前考勤信息") + @GetMapping("/{id}/current-attendance") + public Result 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); + } +} diff --git a/src/main/java/com/attendance/controller/CourseController.java b/src/main/java/com/attendance/controller/CourseController.java new file mode 100644 index 0000000..ccf2526 --- /dev/null +++ b/src/main/java/com/attendance/controller/CourseController.java @@ -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> 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 page = new Page<>(current, size); + return Result.success(courseService.getCoursePage(page, courseName, teacherName, courseType)); + } + + @Operation(summary = "获取课程详情") + @GetMapping("/{id}") + public Result getById(@PathVariable Long id) { + return Result.success(courseService.getById(id)); + } + + @OperationLog(module = "课程管理", action = "新增课程") + @Operation(summary = "新增课程") + @PostMapping + public Result save(@RequestBody Course course) { + courseService.save(course); + return Result.ok("新增成功"); + } + + @OperationLog(module = "课程管理", action = "修改课程") + @Operation(summary = "修改课程") + @PutMapping("/{id}") + public Result update(@PathVariable Long id, @RequestBody Course course) { + course.setId(id); + courseService.updateById(course); + return Result.ok("修改成功"); + } + + @OperationLog(module = "课程管理", action = "删除课程") + @Operation(summary = "删除课程") + @DeleteMapping + public Result remove(@Parameter(description = "课程ID列表") @RequestBody List ids) { + courseService.removeByIds(ids); + return Result.ok("删除成功"); + } + + @Operation(summary = "获取全部课程") + @GetMapping("/list") + public Result> list() { + return Result.success(courseService.list(new LambdaQueryWrapper().eq(Course::getStatus, 1))); + } +} diff --git a/src/main/java/com/attendance/controller/CourseScheduleController.java b/src/main/java/com/attendance/controller/CourseScheduleController.java new file mode 100644 index 0000000..c0cb877 --- /dev/null +++ b/src/main/java/com/attendance/controller/CourseScheduleController.java @@ -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( + @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 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 getById(@PathVariable Long id) { + return Result.success(courseScheduleService.getById(id)); + } + + @OperationLog(module = "课程安排", action = "新增课程安排") + @Operation(summary = "新增课程安排") + @PostMapping + public Result save(@RequestBody CourseSchedule schedule) { + courseScheduleService.saveWithTasks(schedule); + return Result.ok("新增成功"); + } + + @OperationLog(module = "课程安排", action = "修改课程安排") + @Operation(summary = "修改课程安排") + @PutMapping("/{id}") + public Result update(@PathVariable Long id, @RequestBody CourseSchedule schedule) { + schedule.setId(id); + courseScheduleService.updateWithTasks(schedule); + return Result.ok("修改成功"); + } + + @OperationLog(module = "课程安排", action = "删除课程安排") + @Operation(summary = "删除课程安排") + @DeleteMapping + public Result remove(@Parameter(description = "课程安排ID列表") @RequestBody List ids) { + courseScheduleService.removeWithTasks(ids); + return Result.ok("删除成功"); + } + + @Operation(summary = "获取全部课程安排") + @GetMapping("/list") + public Result> list(@RequestParam(required = false) String semester) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (semester != null && !semester.isEmpty()) { + wrapper.eq(CourseSchedule::getSemester, semester); + } + return Result.success(courseScheduleService.list(wrapper)); + } +} diff --git a/src/main/java/com/attendance/controller/DashboardController.java b/src/main/java/com/attendance/controller/DashboardController.java new file mode 100644 index 0000000..f8588ad --- /dev/null +++ b/src/main/java/com/attendance/controller/DashboardController.java @@ -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> getStats() { + List 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 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> getTodayTasks() { + return Result.success(attTaskService.getTasksByDate(LocalDate.now())); + } + + @Operation(summary = "出勤趋势") + @GetMapping("/trend") + public Result> getTrend( + @Parameter(description = "统计天数") @RequestParam(defaultValue = "7") Integer days) { + return Result.success(attTaskService.getAttTrend(days)); + } + + @Operation(summary = "班级出勤排名") + @GetMapping("/ranking") + public Result> getClassRanking( + @Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current, + @Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size) { + Page page = new Page<>(current, size); + return Result.success(attTaskService.getClassRankPage(page)); + } +} diff --git a/src/main/java/com/attendance/controller/DeviceController.java b/src/main/java/com/attendance/controller/DeviceController.java new file mode 100644 index 0000000..fb2c6d2 --- /dev/null +++ b/src/main/java/com/attendance/controller/DeviceController.java @@ -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( + @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 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 getById(@PathVariable Long id) { + return Result.success(deviceService.getById(id)); + } + + @OperationLog(module = "设备管理", action = "新增设备") + @Operation(summary = "新增设备") + @PostMapping + public Result save(@RequestBody Device device) { + deviceService.save(device); + return Result.ok("新增成功"); + } + + @OperationLog(module = "设备管理", action = "修改设备") + @Operation(summary = "修改设备") + @PutMapping("/{id}") + public Result update(@PathVariable Long id, @RequestBody Device device) { + device.setId(id); + deviceService.updateById(device); + return Result.ok("修改成功"); + } + + @OperationLog(module = "设备管理", action = "删除设备") + @Operation(summary = "删除设备") + @DeleteMapping + public Result remove(@Parameter(description = "设备ID列表") @RequestBody List ids) { + deviceService.removeByIds(ids); + return Result.ok("删除成功"); + } + + @OperationLog(module = "设备管理", action = "设备心跳上报") + @Operation(summary = "设备心跳上报") + @PostMapping("/{id}/heartbeat") + public Result 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() { + return Result.success(deviceService.list()); + } +} diff --git a/src/main/java/com/attendance/controller/EduClassController.java b/src/main/java/com/attendance/controller/EduClassController.java new file mode 100644 index 0000000..ea36122 --- /dev/null +++ b/src/main/java/com/attendance/controller/EduClassController.java @@ -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( + @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 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 getById(@PathVariable Long id) { + return Result.success(eduClassService.getById(id)); + } + + @OperationLog(module = "班级管理", action = "新增班级") + @Operation(summary = "新增班级") + @PostMapping + public Result save(@RequestBody EduClass eduClass) { + eduClassService.save(eduClass); + return Result.ok("新增成功"); + } + + @OperationLog(module = "班级管理", action = "修改班级") + @Operation(summary = "修改班级") + @PutMapping("/{id}") + public Result update(@PathVariable Long id, @RequestBody EduClass eduClass) { + eduClass.setId(id); + eduClassService.updateById(eduClass); + return Result.ok("修改成功"); + } + + @OperationLog(module = "班级管理", action = "删除班级") + @Operation(summary = "删除班级") + @DeleteMapping + public Result remove(@Parameter(description = "班级ID列表") @RequestBody List ids) { + eduClassService.removeByIds(ids); + return Result.ok("删除成功"); + } + + @Operation(summary = "获取全部班级") + @GetMapping("/list") + public Result> list() { + return Result.success(eduClassService.list(new LambdaQueryWrapper().eq(EduClass::getStatus, 1))); + } +} diff --git a/src/main/java/com/attendance/controller/PermissionController.java b/src/main/java/com/attendance/controller/PermissionController.java new file mode 100644 index 0000000..56fdef6 --- /dev/null +++ b/src/main/java/com/attendance/controller/PermissionController.java @@ -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() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysPermission::getStatus, 1); + wrapper.orderByAsc(SysPermission::getSortOrder); + return Result.success(sysPermissionService.list(wrapper)); + } + + @Operation(summary = "获取权限树") + @GetMapping("/tree") + public Result> tree() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysPermission::getStatus, 1); + wrapper.orderByAsc(SysPermission::getSortOrder); + return Result.success(sysPermissionService.list(wrapper)); + } + + @Operation(summary = "获取权限详情") + @GetMapping("/{id}") + public Result getById(@PathVariable Long id) { + return Result.success(sysPermissionService.getById(id)); + } + + @OperationLog(module = "权限管理", action = "新增权限") + @Operation(summary = "新增权限") + @PostMapping + public Result save(@RequestBody SysPermission permission) { + sysPermissionService.save(permission); + return Result.ok("新增成功"); + } + + @OperationLog(module = "权限管理", action = "修改权限") + @Operation(summary = "修改权限") + @PutMapping("/{id}") + public Result update(@PathVariable Long id, @RequestBody SysPermission permission) { + permission.setId(id); + sysPermissionService.updateById(permission); + return Result.ok("修改成功"); + } + + @OperationLog(module = "权限管理", action = "删除权限") + @Operation(summary = "删除权限") + @DeleteMapping + public Result remove(@Parameter(description = "权限ID列表") @RequestBody List ids) { + sysPermissionService.removeByIds(ids); + return Result.ok("删除成功"); + } +} diff --git a/src/main/java/com/attendance/controller/RoleController.java b/src/main/java/com/attendance/controller/RoleController.java new file mode 100644 index 0000000..79226de --- /dev/null +++ b/src/main/java/com/attendance/controller/RoleController.java @@ -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( + @RequestParam(defaultValue = "1") Long current, + @RequestParam(defaultValue = "10") Long size) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.orderByAsc(SysRole::getSortOrder); + return Result.success(sysRoleService.page(new Page<>(current, size), wrapper)); + } + + @Operation(summary = "获取角色详情") + @GetMapping("/{id}") + public Result getById(@PathVariable Long id) { + return Result.success(sysRoleService.getById(id)); + } + + @OperationLog(module = "角色管理", action = "新增角色") + @Operation(summary = "新增角色") + @PostMapping + public Result save(@RequestBody SysRole role) { + sysRoleService.save(role); + return Result.ok("新增成功"); + } + + @OperationLog(module = "角色管理", action = "修改角色") + @Operation(summary = "修改角色") + @PutMapping("/{id}") + public Result update(@PathVariable Long id, @RequestBody SysRole role) { + role.setId(id); + sysRoleService.updateById(role); + return Result.ok("修改成功"); + } + + @OperationLog(module = "角色管理", action = "删除角色") + @Operation(summary = "删除角色") + @DeleteMapping + public Result remove(@Parameter(description = "角色ID列表") @RequestBody List ids) { + sysRoleService.removeByIds(ids); + return Result.ok("删除成功"); + } + + @Operation(summary = "获取角色权限列表") + @GetMapping("/{roleId}/permissions") + public Result> getRolePermissions(@PathVariable Long roleId) { + return Result.success(sysPermissionService.getPermissionsByRoleId(roleId)); + } + + @OperationLog(module = "角色管理", action = "分配角色权限") + @Operation(summary = "分配角色权限") + @PostMapping("/{roleId}/permissions") + public Result assignPermissions(@PathVariable Long roleId, @RequestBody List permissionIds) { + return Result.ok("分配成功"); + } + + @Operation(summary = "获取全部角色") + @GetMapping("/list") + public Result> list() { + return Result.success(sysRoleService.list()); + } +} diff --git a/src/main/java/com/attendance/controller/StudentController.java b/src/main/java/com/attendance/controller/StudentController.java new file mode 100644 index 0000000..3cb07af --- /dev/null +++ b/src/main/java/com/attendance/controller/StudentController.java @@ -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> 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 page = new Page<>(current, size); + return Result.success(studentService.getStudentPage(page, classId, keyword)); + } + + @Operation(summary = "获取学生详情") + @GetMapping("/{id}") + public Result getById(@PathVariable Long id) { + return Result.success(studentService.getById(id)); + } + + @OperationLog(module = "学生管理", action = "新增学生") + @Operation(summary = "新增学生") + @PostMapping(consumes = "multipart/form-data") + public Result 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 update(@PathVariable Long id, @RequestBody Student student) { + student.setId(id); + studentService.updateById(student); + return Result.ok("修改成功"); + } + + @OperationLog(module = "学生管理", action = "删除学生") + @Operation(summary = "删除学生") + @DeleteMapping + public Result remove(@Parameter(description = "学生ID列表") @RequestBody List ids) { + studentService.removeStudentsWithFaceData(ids); + return Result.ok("删除成功"); + } + + @Operation(summary = "获取全部学生") + @GetMapping("/list") + public Result> list() { + return Result.success(studentService.list(new LambdaQueryWrapper().eq(Student::getStatus, 1))); + } +} diff --git a/src/main/java/com/attendance/controller/SysUserController.java b/src/main/java/com/attendance/controller/SysUserController.java new file mode 100644 index 0000000..597e1b0 --- /dev/null +++ b/src/main/java/com/attendance/controller/SysUserController.java @@ -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( + @Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current, + @Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size, + @Parameter(description = "关键词(用户名/真实姓名)") @RequestParam(required = false) String keyword) { + LambdaQueryWrapper 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 getById(@PathVariable Long id) { + return Result.success(sysUserService.getById(id)); + } + + @OperationLog(module = "用户管理", action = "新增用户") + @Operation(summary = "新增用户") + @PostMapping + public Result 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 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 remove(@Parameter(description = "用户ID列表") @RequestBody List ids) { + sysUserService.removeByIds(ids); + return Result.ok("删除成功"); + } + + @OperationLog(module = "用户管理", action = "修改密码") + @Operation(summary = "修改密码") + @PutMapping("/{id}/password") + public Result updatePassword(@PathVariable Long id, @RequestBody PasswordDTO passwordDTO) { + sysUserService.updatePassword(id, passwordDTO); + return Result.ok("密码修改成功"); + } + + @Operation(summary = "获取全部用户") + @GetMapping("/list") + public Result> list() { + return Result.success(sysUserService.list()); + } +} diff --git a/src/main/java/com/attendance/controller/TeacherController.java b/src/main/java/com/attendance/controller/TeacherController.java new file mode 100644 index 0000000..dfaf89d --- /dev/null +++ b/src/main/java/com/attendance/controller/TeacherController.java @@ -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( + @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 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 getById(@PathVariable Long id) { + return Result.success(teacherService.getById(id)); + } + + @OperationLog(module = "教师管理", action = "新增教师") + @Operation(summary = "新增教师") + @PostMapping + public Result save(@RequestBody Teacher teacher) { + teacherService.save(teacher); + return Result.ok("新增成功"); + } + + @OperationLog(module = "教师管理", action = "修改教师") + @Operation(summary = "修改教师") + @PutMapping("/{id}") + public Result update(@PathVariable Long id, @RequestBody Teacher teacher) { + teacher.setId(id); + teacherService.updateById(teacher); + return Result.ok("修改成功"); + } + + @OperationLog(module = "教师管理", action = "删除教师") + @Operation(summary = "删除教师") + @DeleteMapping + public Result remove(@Parameter(description = "教师ID列表") @RequestBody List ids) { + teacherService.removeByIds(ids); + return Result.ok("删除成功"); + } + + @Operation(summary = "获取全部教师") + @GetMapping("/list") + public Result> list() { + return Result.success(teacherService.list(new LambdaQueryWrapper().eq(Teacher::getStatus, 1))); + } +} diff --git a/src/main/java/com/attendance/dto/LoginDTO.java b/src/main/java/com/attendance/dto/LoginDTO.java new file mode 100644 index 0000000..ccbe9da --- /dev/null +++ b/src/main/java/com/attendance/dto/LoginDTO.java @@ -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; +} diff --git a/src/main/java/com/attendance/dto/LoginResultDTO.java b/src/main/java/com/attendance/dto/LoginResultDTO.java new file mode 100644 index 0000000..302d4ca --- /dev/null +++ b/src/main/java/com/attendance/dto/LoginResultDTO.java @@ -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; +} diff --git a/src/main/java/com/attendance/dto/PageDTO.java b/src/main/java/com/attendance/dto/PageDTO.java new file mode 100644 index 0000000..22011be --- /dev/null +++ b/src/main/java/com/attendance/dto/PageDTO.java @@ -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; +} diff --git a/src/main/java/com/attendance/dto/PasswordDTO.java b/src/main/java/com/attendance/dto/PasswordDTO.java new file mode 100644 index 0000000..4c8165a --- /dev/null +++ b/src/main/java/com/attendance/dto/PasswordDTO.java @@ -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; +} diff --git a/src/main/java/com/attendance/dto/StudentDTO.java b/src/main/java/com/attendance/dto/StudentDTO.java new file mode 100644 index 0000000..0c307d7 --- /dev/null +++ b/src/main/java/com/attendance/dto/StudentDTO.java @@ -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 frontImage; + + @Schema(description = "左脸样本图片数组,可传多张") + private List leftImage; + + @Schema(description = "右脸样本图片数组,可传多张") + private List rightImage; +} diff --git a/src/main/java/com/attendance/entity/AttDetail.java b/src/main/java/com/attendance/entity/AttDetail.java new file mode 100644 index 0000000..5e118fd --- /dev/null +++ b/src/main/java/com/attendance/entity/AttDetail.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/AttRecord.java b/src/main/java/com/attendance/entity/AttRecord.java new file mode 100644 index 0000000..0c189af --- /dev/null +++ b/src/main/java/com/attendance/entity/AttRecord.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/AttRule.java b/src/main/java/com/attendance/entity/AttRule.java new file mode 100644 index 0000000..f41dd7c --- /dev/null +++ b/src/main/java/com/attendance/entity/AttRule.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/AttTask.java b/src/main/java/com/attendance/entity/AttTask.java new file mode 100644 index 0000000..a2e18c6 --- /dev/null +++ b/src/main/java/com/attendance/entity/AttTask.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/BaseEntity.java b/src/main/java/com/attendance/entity/BaseEntity.java new file mode 100644 index 0000000..8c98ec2 --- /dev/null +++ b/src/main/java/com/attendance/entity/BaseEntity.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/BehaviorRecord.java b/src/main/java/com/attendance/entity/BehaviorRecord.java new file mode 100644 index 0000000..562b5c9 --- /dev/null +++ b/src/main/java/com/attendance/entity/BehaviorRecord.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/BehaviorType.java b/src/main/java/com/attendance/entity/BehaviorType.java new file mode 100644 index 0000000..e04f40c --- /dev/null +++ b/src/main/java/com/attendance/entity/BehaviorType.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/Building.java b/src/main/java/com/attendance/entity/Building.java new file mode 100644 index 0000000..bfabca8 --- /dev/null +++ b/src/main/java/com/attendance/entity/Building.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/Classroom.java b/src/main/java/com/attendance/entity/Classroom.java new file mode 100644 index 0000000..b90cdf1 --- /dev/null +++ b/src/main/java/com/attendance/entity/Classroom.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/Course.java b/src/main/java/com/attendance/entity/Course.java new file mode 100644 index 0000000..0a3bc38 --- /dev/null +++ b/src/main/java/com/attendance/entity/Course.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/CourseSchedule.java b/src/main/java/com/attendance/entity/CourseSchedule.java new file mode 100644 index 0000000..1eb0c54 --- /dev/null +++ b/src/main/java/com/attendance/entity/CourseSchedule.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/Device.java b/src/main/java/com/attendance/entity/Device.java new file mode 100644 index 0000000..ce15197 --- /dev/null +++ b/src/main/java/com/attendance/entity/Device.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/EduClass.java b/src/main/java/com/attendance/entity/EduClass.java new file mode 100644 index 0000000..a9973ac --- /dev/null +++ b/src/main/java/com/attendance/entity/EduClass.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/FaceIdentity.java b/src/main/java/com/attendance/entity/FaceIdentity.java new file mode 100644 index 0000000..f06b072 --- /dev/null +++ b/src/main/java/com/attendance/entity/FaceIdentity.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/FaceSample.java b/src/main/java/com/attendance/entity/FaceSample.java new file mode 100644 index 0000000..f9b3c48 --- /dev/null +++ b/src/main/java/com/attendance/entity/FaceSample.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/MonitorRecord.java b/src/main/java/com/attendance/entity/MonitorRecord.java new file mode 100644 index 0000000..d30fa62 --- /dev/null +++ b/src/main/java/com/attendance/entity/MonitorRecord.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/Student.java b/src/main/java/com/attendance/entity/Student.java new file mode 100644 index 0000000..e6a9458 --- /dev/null +++ b/src/main/java/com/attendance/entity/Student.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/SysOperationLog.java b/src/main/java/com/attendance/entity/SysOperationLog.java new file mode 100644 index 0000000..eed3390 --- /dev/null +++ b/src/main/java/com/attendance/entity/SysOperationLog.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/SysPermission.java b/src/main/java/com/attendance/entity/SysPermission.java new file mode 100644 index 0000000..fbd8619 --- /dev/null +++ b/src/main/java/com/attendance/entity/SysPermission.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/SysRole.java b/src/main/java/com/attendance/entity/SysRole.java new file mode 100644 index 0000000..28a2a18 --- /dev/null +++ b/src/main/java/com/attendance/entity/SysRole.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/SysRolePermission.java b/src/main/java/com/attendance/entity/SysRolePermission.java new file mode 100644 index 0000000..f2b1b79 --- /dev/null +++ b/src/main/java/com/attendance/entity/SysRolePermission.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/SysSchool.java b/src/main/java/com/attendance/entity/SysSchool.java new file mode 100644 index 0000000..874b334 --- /dev/null +++ b/src/main/java/com/attendance/entity/SysSchool.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/SysUser.java b/src/main/java/com/attendance/entity/SysUser.java new file mode 100644 index 0000000..08cf8b8 --- /dev/null +++ b/src/main/java/com/attendance/entity/SysUser.java @@ -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; +} diff --git a/src/main/java/com/attendance/entity/Teacher.java b/src/main/java/com/attendance/entity/Teacher.java new file mode 100644 index 0000000..b807628 --- /dev/null +++ b/src/main/java/com/attendance/entity/Teacher.java @@ -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; +} diff --git a/src/main/java/com/attendance/mapper/AttDetailMapper.java b/src/main/java/com/attendance/mapper/AttDetailMapper.java new file mode 100644 index 0000000..f189180 --- /dev/null +++ b/src/main/java/com/attendance/mapper/AttDetailMapper.java @@ -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 { + + @Select("SELECT * FROM att_detail WHERE task_id = #{taskId} AND deleted = 0 ORDER BY id") + List selectByTaskId(@Param("taskId") Long taskId); +} diff --git a/src/main/java/com/attendance/mapper/AttRecordMapper.java b/src/main/java/com/attendance/mapper/AttRecordMapper.java new file mode 100644 index 0000000..138b911 --- /dev/null +++ b/src/main/java/com/attendance/mapper/AttRecordMapper.java @@ -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 { + + IPage selectHistoryPage(Page page, + @Param("courseName") String courseName, + @Param("attDate") String attDate); +} diff --git a/src/main/java/com/attendance/mapper/AttRuleMapper.java b/src/main/java/com/attendance/mapper/AttRuleMapper.java new file mode 100644 index 0000000..5c11d3d --- /dev/null +++ b/src/main/java/com/attendance/mapper/AttRuleMapper.java @@ -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 { +} diff --git a/src/main/java/com/attendance/mapper/AttTaskMapper.java b/src/main/java/com/attendance/mapper/AttTaskMapper.java new file mode 100644 index 0000000..2a46543 --- /dev/null +++ b/src/main/java/com/attendance/mapper/AttTaskMapper.java @@ -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 { + + @Select("SELECT * FROM att_task WHERE att_date = #{date} AND deleted = 0 ORDER BY start_time DESC") + List selectByDate(@Param("date") LocalDate date); + + IPage selectTaskPage(Page page, + @Param("attDate") LocalDate attDate, + @Param("keyword") String keyword, + @Param("taskStatus") Integer taskStatus); + + IPage selectClassRankPage(Page page); + + List selectAttTrend(@Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate); +} diff --git a/src/main/java/com/attendance/mapper/BehaviorRecordMapper.java b/src/main/java/com/attendance/mapper/BehaviorRecordMapper.java new file mode 100644 index 0000000..c7a505c --- /dev/null +++ b/src/main/java/com/attendance/mapper/BehaviorRecordMapper.java @@ -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 { + + @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> selectBehaviorStatsByTaskId(@Param("taskId") Long taskId); + + @Select("") + List> selectBehaviorStatsWithFilters( + @Param("courseId") Long courseId, + @Param("teacherId") Long teacherId, + @Param("attDate") LocalDate attDate); + + @Select("") + List> selectTimePeriodStats( + @Param("courseId") Long courseId, + @Param("teacherId") Long teacherId, + @Param("attDate") LocalDate attDate); + + @Select("") + IPage selectBehaviorRecordPage( + IPage page, + @Param("courseId") Long courseId, + @Param("teacherId") Long teacherId, + @Param("attDate") LocalDate attDate, + @Param("behaviorTypeId") Long behaviorTypeId); +} diff --git a/src/main/java/com/attendance/mapper/BehaviorTypeMapper.java b/src/main/java/com/attendance/mapper/BehaviorTypeMapper.java new file mode 100644 index 0000000..02854a4 --- /dev/null +++ b/src/main/java/com/attendance/mapper/BehaviorTypeMapper.java @@ -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 { +} diff --git a/src/main/java/com/attendance/mapper/BuildingMapper.java b/src/main/java/com/attendance/mapper/BuildingMapper.java new file mode 100644 index 0000000..706213e --- /dev/null +++ b/src/main/java/com/attendance/mapper/BuildingMapper.java @@ -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 { + + IPage selectBuildingPage(Page page, + @Param("keyword") String keyword); +} diff --git a/src/main/java/com/attendance/mapper/ClassroomMapper.java b/src/main/java/com/attendance/mapper/ClassroomMapper.java new file mode 100644 index 0000000..58a22e9 --- /dev/null +++ b/src/main/java/com/attendance/mapper/ClassroomMapper.java @@ -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 { + + IPage selectClassroomPage(Page page, + @Param("buildingId") Long buildingId); + + List selectClassroomList(@Param("buildingId") Long buildingId); +} diff --git a/src/main/java/com/attendance/mapper/CourseMapper.java b/src/main/java/com/attendance/mapper/CourseMapper.java new file mode 100644 index 0000000..656b661 --- /dev/null +++ b/src/main/java/com/attendance/mapper/CourseMapper.java @@ -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 { + + IPage selectCoursePage(Page page, + @Param("courseName") String courseName, + @Param("teacherName") String teacherName, + @Param("courseType") String courseType); +} diff --git a/src/main/java/com/attendance/mapper/CourseScheduleMapper.java b/src/main/java/com/attendance/mapper/CourseScheduleMapper.java new file mode 100644 index 0000000..4804839 --- /dev/null +++ b/src/main/java/com/attendance/mapper/CourseScheduleMapper.java @@ -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 { +} diff --git a/src/main/java/com/attendance/mapper/DeviceMapper.java b/src/main/java/com/attendance/mapper/DeviceMapper.java new file mode 100644 index 0000000..68a0542 --- /dev/null +++ b/src/main/java/com/attendance/mapper/DeviceMapper.java @@ -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 { +} diff --git a/src/main/java/com/attendance/mapper/EduClassMapper.java b/src/main/java/com/attendance/mapper/EduClassMapper.java new file mode 100644 index 0000000..4dff7a9 --- /dev/null +++ b/src/main/java/com/attendance/mapper/EduClassMapper.java @@ -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 { +} diff --git a/src/main/java/com/attendance/mapper/FaceIdentityMapper.java b/src/main/java/com/attendance/mapper/FaceIdentityMapper.java new file mode 100644 index 0000000..97b42bc --- /dev/null +++ b/src/main/java/com/attendance/mapper/FaceIdentityMapper.java @@ -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 { +} diff --git a/src/main/java/com/attendance/mapper/FaceSampleMapper.java b/src/main/java/com/attendance/mapper/FaceSampleMapper.java new file mode 100644 index 0000000..d94215a --- /dev/null +++ b/src/main/java/com/attendance/mapper/FaceSampleMapper.java @@ -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 { +} diff --git a/src/main/java/com/attendance/mapper/MonitorRecordMapper.java b/src/main/java/com/attendance/mapper/MonitorRecordMapper.java new file mode 100644 index 0000000..5d9bd8e --- /dev/null +++ b/src/main/java/com/attendance/mapper/MonitorRecordMapper.java @@ -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 { +} diff --git a/src/main/java/com/attendance/mapper/StudentMapper.java b/src/main/java/com/attendance/mapper/StudentMapper.java new file mode 100644 index 0000000..f461da3 --- /dev/null +++ b/src/main/java/com/attendance/mapper/StudentMapper.java @@ -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 { + + IPage selectStudentPage(Page page, + @Param("classId") Long classId, + @Param("keyword") String keyword); +} diff --git a/src/main/java/com/attendance/mapper/SysOperationLogMapper.java b/src/main/java/com/attendance/mapper/SysOperationLogMapper.java new file mode 100644 index 0000000..d9e0d43 --- /dev/null +++ b/src/main/java/com/attendance/mapper/SysOperationLogMapper.java @@ -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 { +} diff --git a/src/main/java/com/attendance/mapper/SysPermissionMapper.java b/src/main/java/com/attendance/mapper/SysPermissionMapper.java new file mode 100644 index 0000000..8e18d9b --- /dev/null +++ b/src/main/java/com/attendance/mapper/SysPermissionMapper.java @@ -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 { + + @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 selectByRoleId(@Param("roleId") Long roleId); +} diff --git a/src/main/java/com/attendance/mapper/SysRoleMapper.java b/src/main/java/com/attendance/mapper/SysRoleMapper.java new file mode 100644 index 0000000..5855170 --- /dev/null +++ b/src/main/java/com/attendance/mapper/SysRoleMapper.java @@ -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 { +} diff --git a/src/main/java/com/attendance/mapper/SysRolePermissionMapper.java b/src/main/java/com/attendance/mapper/SysRolePermissionMapper.java new file mode 100644 index 0000000..9c1f5d2 --- /dev/null +++ b/src/main/java/com/attendance/mapper/SysRolePermissionMapper.java @@ -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 { +} diff --git a/src/main/java/com/attendance/mapper/SysSchoolMapper.java b/src/main/java/com/attendance/mapper/SysSchoolMapper.java new file mode 100644 index 0000000..a4feb91 --- /dev/null +++ b/src/main/java/com/attendance/mapper/SysSchoolMapper.java @@ -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 { +} diff --git a/src/main/java/com/attendance/mapper/SysUserMapper.java b/src/main/java/com/attendance/mapper/SysUserMapper.java new file mode 100644 index 0000000..0950575 --- /dev/null +++ b/src/main/java/com/attendance/mapper/SysUserMapper.java @@ -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 { + + @Select("SELECT * FROM sys_user WHERE username = #{username} AND deleted = 0 LIMIT 1") + SysUser selectByUsername(String username); +} diff --git a/src/main/java/com/attendance/mapper/TeacherMapper.java b/src/main/java/com/attendance/mapper/TeacherMapper.java new file mode 100644 index 0000000..e3ed21d --- /dev/null +++ b/src/main/java/com/attendance/mapper/TeacherMapper.java @@ -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 { +} diff --git a/src/main/java/com/attendance/service/AttDetailService.java b/src/main/java/com/attendance/service/AttDetailService.java new file mode 100644 index 0000000..8d0779c --- /dev/null +++ b/src/main/java/com/attendance/service/AttDetailService.java @@ -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 { + + List getDetailsByTaskId(Long taskId); +} diff --git a/src/main/java/com/attendance/service/AttRecordService.java b/src/main/java/com/attendance/service/AttRecordService.java new file mode 100644 index 0000000..a3f4ef8 --- /dev/null +++ b/src/main/java/com/attendance/service/AttRecordService.java @@ -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 { + + IPage getHistoryPage(IPage page, String courseName, String attDate); +} diff --git a/src/main/java/com/attendance/service/AttRuleService.java b/src/main/java/com/attendance/service/AttRuleService.java new file mode 100644 index 0000000..4e850fb --- /dev/null +++ b/src/main/java/com/attendance/service/AttRuleService.java @@ -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 { +} diff --git a/src/main/java/com/attendance/service/AttTaskService.java b/src/main/java/com/attendance/service/AttTaskService.java new file mode 100644 index 0000000..fb8c765 --- /dev/null +++ b/src/main/java/com/attendance/service/AttTaskService.java @@ -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 { + + List getTasksByDate(LocalDate date); + + IPage getTaskPage(Page page, LocalDate attDate, String keyword, Integer taskStatus); + + IPage getClassRankPage(Page page); + + List getAttTrend(Integer days); +} diff --git a/src/main/java/com/attendance/service/BehaviorRecordService.java b/src/main/java/com/attendance/service/BehaviorRecordService.java new file mode 100644 index 0000000..5a29213 --- /dev/null +++ b/src/main/java/com/attendance/service/BehaviorRecordService.java @@ -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 { + + List> getBehaviorStatsByTaskId(Long taskId); + + List> getBehaviorStatsWithFilters(Long courseId, Long teacherId, LocalDate attDate); + + List> getTimePeriodStats(Long courseId, Long teacherId, LocalDate attDate); + + IPage getBehaviorRecordPage(IPage page, Long courseId, Long teacherId, LocalDate attDate, Long behaviorTypeId); +} diff --git a/src/main/java/com/attendance/service/BehaviorTypeService.java b/src/main/java/com/attendance/service/BehaviorTypeService.java new file mode 100644 index 0000000..b4ef4a2 --- /dev/null +++ b/src/main/java/com/attendance/service/BehaviorTypeService.java @@ -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 { +} diff --git a/src/main/java/com/attendance/service/BuildingService.java b/src/main/java/com/attendance/service/BuildingService.java new file mode 100644 index 0000000..6ea1a3d --- /dev/null +++ b/src/main/java/com/attendance/service/BuildingService.java @@ -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 { + + IPage getBuildingPage(IPage page, String keyword); + + @Transactional(rollbackFor = Exception.class) + boolean removeByIds(List ids); +} diff --git a/src/main/java/com/attendance/service/ClassroomService.java b/src/main/java/com/attendance/service/ClassroomService.java new file mode 100644 index 0000000..587fee9 --- /dev/null +++ b/src/main/java/com/attendance/service/ClassroomService.java @@ -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 { + + IPage getClassroomPage(IPage page, Long buildingId); + + List getClassroomList(Long buildingId); + + ClassroomAttendanceVO getCurrentAttendance(Long classroomId, LocalDateTime currentTime); + + @Transactional(rollbackFor = Exception.class) + boolean removeByIds(List ids); +} diff --git a/src/main/java/com/attendance/service/CourseScheduleService.java b/src/main/java/com/attendance/service/CourseScheduleService.java new file mode 100644 index 0000000..cbb08de --- /dev/null +++ b/src/main/java/com/attendance/service/CourseScheduleService.java @@ -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 { + + void saveWithTasks(CourseSchedule schedule); + + void updateWithTasks(CourseSchedule schedule); + + void removeWithTasks(List ids); +} diff --git a/src/main/java/com/attendance/service/CourseService.java b/src/main/java/com/attendance/service/CourseService.java new file mode 100644 index 0000000..d30c32b --- /dev/null +++ b/src/main/java/com/attendance/service/CourseService.java @@ -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 { + + IPage getCoursePage(IPage page, String courseName, String teacherName, String courseType); +} diff --git a/src/main/java/com/attendance/service/DeviceService.java b/src/main/java/com/attendance/service/DeviceService.java new file mode 100644 index 0000000..c4b3f31 --- /dev/null +++ b/src/main/java/com/attendance/service/DeviceService.java @@ -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 { +} diff --git a/src/main/java/com/attendance/service/EduClassService.java b/src/main/java/com/attendance/service/EduClassService.java new file mode 100644 index 0000000..85a4643 --- /dev/null +++ b/src/main/java/com/attendance/service/EduClassService.java @@ -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 { +} diff --git a/src/main/java/com/attendance/service/FaceIdentityService.java b/src/main/java/com/attendance/service/FaceIdentityService.java new file mode 100644 index 0000000..56f1f13 --- /dev/null +++ b/src/main/java/com/attendance/service/FaceIdentityService.java @@ -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 { +} diff --git a/src/main/java/com/attendance/service/FaceSampleService.java b/src/main/java/com/attendance/service/FaceSampleService.java new file mode 100644 index 0000000..d8230d7 --- /dev/null +++ b/src/main/java/com/attendance/service/FaceSampleService.java @@ -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 { + + void addFaceSample(FaceSample faceSample); +} diff --git a/src/main/java/com/attendance/service/MonitorRecordService.java b/src/main/java/com/attendance/service/MonitorRecordService.java new file mode 100644 index 0000000..4e269cf --- /dev/null +++ b/src/main/java/com/attendance/service/MonitorRecordService.java @@ -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 { +} diff --git a/src/main/java/com/attendance/service/StudentService.java b/src/main/java/com/attendance/service/StudentService.java new file mode 100644 index 0000000..3b59fde --- /dev/null +++ b/src/main/java/com/attendance/service/StudentService.java @@ -0,0 +1,18 @@ +package com.attendance.service; + +import com.attendance.dto.StudentDTO; +import com.attendance.entity.Student; +import com.attendance.vo.StudentVO; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +public interface StudentService extends IService { + + void addStudentWithFaceSamples(StudentDTO dto) throws Exception; + + void removeStudentsWithFaceData(List ids); + + IPage getStudentPage(IPage page, Long classId, String keyword); +} diff --git a/src/main/java/com/attendance/service/SysOperationLogService.java b/src/main/java/com/attendance/service/SysOperationLogService.java new file mode 100644 index 0000000..9deb050 --- /dev/null +++ b/src/main/java/com/attendance/service/SysOperationLogService.java @@ -0,0 +1,10 @@ +package com.attendance.service; + +import com.attendance.entity.SysOperationLog; +import com.baomidou.mybatisplus.extension.service.IService; + +public interface SysOperationLogService extends IService { + + /** 异步保存操作日志 */ + void asyncSave(SysOperationLog log); +} diff --git a/src/main/java/com/attendance/service/SysPermissionService.java b/src/main/java/com/attendance/service/SysPermissionService.java new file mode 100644 index 0000000..8a8e872 --- /dev/null +++ b/src/main/java/com/attendance/service/SysPermissionService.java @@ -0,0 +1,11 @@ +package com.attendance.service; + +import com.attendance.entity.SysPermission; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +public interface SysPermissionService extends IService { + + List getPermissionsByRoleId(Long roleId); +} diff --git a/src/main/java/com/attendance/service/SysRoleService.java b/src/main/java/com/attendance/service/SysRoleService.java new file mode 100644 index 0000000..f4ec38e --- /dev/null +++ b/src/main/java/com/attendance/service/SysRoleService.java @@ -0,0 +1,7 @@ +package com.attendance.service; + +import com.attendance.entity.SysRole; +import com.baomidou.mybatisplus.extension.service.IService; + +public interface SysRoleService extends IService { +} diff --git a/src/main/java/com/attendance/service/SysSchoolService.java b/src/main/java/com/attendance/service/SysSchoolService.java new file mode 100644 index 0000000..e3c7d79 --- /dev/null +++ b/src/main/java/com/attendance/service/SysSchoolService.java @@ -0,0 +1,7 @@ +package com.attendance.service; + +import com.attendance.entity.SysSchool; +import com.baomidou.mybatisplus.extension.service.IService; + +public interface SysSchoolService extends IService { +} diff --git a/src/main/java/com/attendance/service/SysUserService.java b/src/main/java/com/attendance/service/SysUserService.java new file mode 100644 index 0000000..4452539 --- /dev/null +++ b/src/main/java/com/attendance/service/SysUserService.java @@ -0,0 +1,16 @@ +package com.attendance.service; + +import com.attendance.dto.LoginDTO; +import com.attendance.dto.LoginResultDTO; +import com.attendance.dto.PasswordDTO; +import com.attendance.entity.SysUser; +import com.baomidou.mybatisplus.extension.service.IService; + +public interface SysUserService extends IService { + + LoginResultDTO login(LoginDTO loginDTO); + + SysUser getByUsername(String username); + + void updatePassword(Long userId, PasswordDTO passwordDTO); +} diff --git a/src/main/java/com/attendance/service/TeacherService.java b/src/main/java/com/attendance/service/TeacherService.java new file mode 100644 index 0000000..fcfc308 --- /dev/null +++ b/src/main/java/com/attendance/service/TeacherService.java @@ -0,0 +1,7 @@ +package com.attendance.service; + +import com.attendance.entity.Teacher; +import com.baomidou.mybatisplus.extension.service.IService; + +public interface TeacherService extends IService { +} diff --git a/src/main/java/com/attendance/service/impl/AttDetailServiceImpl.java b/src/main/java/com/attendance/service/impl/AttDetailServiceImpl.java new file mode 100644 index 0000000..ffc88cc --- /dev/null +++ b/src/main/java/com/attendance/service/impl/AttDetailServiceImpl.java @@ -0,0 +1,22 @@ +package com.attendance.service.impl; + +import com.attendance.entity.AttDetail; +import com.attendance.mapper.AttDetailMapper; +import com.attendance.service.AttDetailService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class AttDetailServiceImpl extends ServiceImpl implements AttDetailService { + + private final AttDetailMapper attDetailMapper; + + @Override + public List getDetailsByTaskId(Long taskId) { + return attDetailMapper.selectByTaskId(taskId); + } +} diff --git a/src/main/java/com/attendance/service/impl/AttRecordServiceImpl.java b/src/main/java/com/attendance/service/impl/AttRecordServiceImpl.java new file mode 100644 index 0000000..15e5f4f --- /dev/null +++ b/src/main/java/com/attendance/service/impl/AttRecordServiceImpl.java @@ -0,0 +1,35 @@ +package com.attendance.service.impl; + +import com.attendance.entity.AttDetail; +import com.attendance.entity.AttRecord; +import com.attendance.entity.Student; +import com.attendance.mapper.AttRecordMapper; +import com.attendance.service.AttDetailService; +import com.attendance.service.AttRecordService; +import com.attendance.service.StudentService; +import com.attendance.vo.AttRecordVO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class AttRecordServiceImpl extends ServiceImpl implements AttRecordService { + + private final AttDetailService attDetailService; + private final StudentService studentService; + + public AttRecordServiceImpl(AttDetailService attDetailService, StudentService studentService) { + this.attDetailService = attDetailService; + this.studentService = studentService; + } + + @Override + public IPage getHistoryPage(IPage page, String courseName, String attDate) { + return baseMapper.selectHistoryPage((Page) page, courseName, attDate); + } +} diff --git a/src/main/java/com/attendance/service/impl/AttRuleServiceImpl.java b/src/main/java/com/attendance/service/impl/AttRuleServiceImpl.java new file mode 100644 index 0000000..0420a34 --- /dev/null +++ b/src/main/java/com/attendance/service/impl/AttRuleServiceImpl.java @@ -0,0 +1,11 @@ +package com.attendance.service.impl; + +import com.attendance.entity.AttRule; +import com.attendance.mapper.AttRuleMapper; +import com.attendance.service.AttRuleService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +@Service +public class AttRuleServiceImpl extends ServiceImpl implements AttRuleService { +} diff --git a/src/main/java/com/attendance/service/impl/AttTaskServiceImpl.java b/src/main/java/com/attendance/service/impl/AttTaskServiceImpl.java new file mode 100644 index 0000000..77b7c66 --- /dev/null +++ b/src/main/java/com/attendance/service/impl/AttTaskServiceImpl.java @@ -0,0 +1,69 @@ +package com.attendance.service.impl; + +import com.attendance.entity.AttTask; +import com.attendance.mapper.AttTaskMapper; +import com.attendance.service.AttTaskService; +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.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class AttTaskServiceImpl extends ServiceImpl implements AttTaskService { + + private final AttTaskMapper attTaskMapper; + + @Override + public List getTasksByDate(LocalDate date) { + return attTaskMapper.selectByDate(date); + } + + @Override + public IPage getTaskPage(Page page, LocalDate attDate, String keyword, Integer taskStatus) { + return attTaskMapper.selectTaskPage(page, attDate, keyword, taskStatus); + } + + @Override + public IPage getClassRankPage(Page page) { + return attTaskMapper.selectClassRankPage(page); + } + + @Override + public List getAttTrend(Integer days) { + LocalDate endDate = LocalDate.now(); + LocalDate startDate = endDate.minusDays(days - 1); + List dbData = attTaskMapper.selectAttTrend(startDate, endDate); + Map dataMap = dbData.stream() + .collect(Collectors.toMap(AttTrendVO::getDate, v -> v)); + List result = new ArrayList<>(); + DateTimeFormatter fmt = DateTimeFormatter.ofPattern("MM/dd"); + for (LocalDate d = startDate; !d.isAfter(endDate); d = d.plusDays(1)) { + String dateStr = d.format(fmt); + AttTrendVO vo = dataMap.get(dateStr); + if (vo != null) { + result.add(vo); + } else { + AttTrendVO empty = new AttTrendVO(); + empty.setDate(dateStr); + empty.setTotalCount(0); + empty.setActualCount(0); + empty.setAttendanceRate(BigDecimal.ZERO); + result.add(empty); + } + } + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/attendance/service/impl/BehaviorRecordServiceImpl.java b/src/main/java/com/attendance/service/impl/BehaviorRecordServiceImpl.java new file mode 100644 index 0000000..1b6a4b9 --- /dev/null +++ b/src/main/java/com/attendance/service/impl/BehaviorRecordServiceImpl.java @@ -0,0 +1,73 @@ +package com.attendance.service.impl; + +import com.attendance.vo.BehaviorRecordVO; +import com.attendance.entity.BehaviorRecord; +import com.attendance.entity.BehaviorType; +import com.attendance.mapper.BehaviorRecordMapper; +import com.attendance.service.BehaviorRecordService; +import com.attendance.service.BehaviorTypeService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class BehaviorRecordServiceImpl extends ServiceImpl implements BehaviorRecordService { + + private final BehaviorRecordMapper behaviorRecordMapper; + private final BehaviorTypeService behaviorTypeService; + + @Override + public List> getBehaviorStatsByTaskId(Long taskId) { + return behaviorRecordMapper.selectBehaviorStatsByTaskId(taskId); + } + + @Override + public List> getBehaviorStatsWithFilters(Long courseId, Long teacherId, LocalDate attDate) { + return behaviorRecordMapper.selectBehaviorStatsWithFilters(courseId, teacherId, attDate); + } + + @Override + public List> getTimePeriodStats(Long courseId, Long teacherId, LocalDate attDate) { + List> stats = behaviorRecordMapper.selectTimePeriodStats(courseId, teacherId, attDate); + // 查询所有启用的行为类型(含颜色) + List allTypes = behaviorTypeService.list( + new LambdaQueryWrapper().eq(BehaviorType::getStatus, 1).orderByAsc(BehaviorType::getSortOrder)); + // 构建 "时段+行为类型" 到 value 的映射 + Map statsMap = stats.stream() + .collect(Collectors.toMap( + m -> m.get("timeSlot") + "_" + m.get("typeName"), + m -> m.get("value"), + (v1, v2) -> v1)); + // 按时段分组,每时段下包含所有行为类型数据 + List> result = new java.util.ArrayList<>(); + for (int h = 8; h < 22; h += 2) { + String slot = String.format("%02d:00-%02d:00", h, h + 2); + List> data = new java.util.ArrayList<>(); + for (BehaviorType type : allTypes) { + Map item = new java.util.LinkedHashMap<>(); + item.put("name", type.getTypeName()); + item.put("value", ((Number) statsMap.getOrDefault(slot + "_" + type.getTypeName(), 0)).intValue()); + item.put("color", type.getColor()); + data.add(item); + } + Map period = new java.util.LinkedHashMap<>(); + period.put("timeSlot", slot); + period.put("data", data); + result.add(period); + } + return result; + } + + @Override + public IPage getBehaviorRecordPage(IPage page, Long courseId, Long teacherId, LocalDate attDate, Long behaviorTypeId) { + return behaviorRecordMapper.selectBehaviorRecordPage(page, courseId, teacherId, attDate, behaviorTypeId); + } +} diff --git a/src/main/java/com/attendance/service/impl/BehaviorTypeServiceImpl.java b/src/main/java/com/attendance/service/impl/BehaviorTypeServiceImpl.java new file mode 100644 index 0000000..0fc2237 --- /dev/null +++ b/src/main/java/com/attendance/service/impl/BehaviorTypeServiceImpl.java @@ -0,0 +1,11 @@ +package com.attendance.service.impl; + +import com.attendance.entity.BehaviorType; +import com.attendance.mapper.BehaviorTypeMapper; +import com.attendance.service.BehaviorTypeService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +@Service +public class BehaviorTypeServiceImpl extends ServiceImpl implements BehaviorTypeService { +} diff --git a/src/main/java/com/attendance/service/impl/BuildingServiceImpl.java b/src/main/java/com/attendance/service/impl/BuildingServiceImpl.java new file mode 100644 index 0000000..d132b60 --- /dev/null +++ b/src/main/java/com/attendance/service/impl/BuildingServiceImpl.java @@ -0,0 +1,56 @@ +package com.attendance.service.impl; + +import com.attendance.entity.Building; +import com.attendance.entity.Classroom; +import com.attendance.entity.Device; +import com.attendance.mapper.BuildingMapper; +import com.attendance.mapper.ClassroomMapper; +import com.attendance.mapper.DeviceMapper; +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 com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class BuildingServiceImpl extends ServiceImpl implements BuildingService { + + private final ClassroomMapper classroomMapper; + private final DeviceMapper deviceMapper; + + public BuildingServiceImpl(ClassroomMapper classroomMapper, DeviceMapper deviceMapper) { + this.classroomMapper = classroomMapper; + this.deviceMapper = deviceMapper; + } + + @Override + public IPage getBuildingPage(IPage page, String keyword) { + return baseMapper.selectBuildingPage((Page) page, keyword); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean removeByIds(List ids) { + if (ids == null || ids.isEmpty()) { + return false; + } + // 1. 查找这些教学楼下的所有教室 + List classrooms = classroomMapper.selectList( + new LambdaQueryWrapper().in(Classroom::getBuildingId, ids)); + if (!classrooms.isEmpty()) { + List classroomIds = classrooms.stream().map(Classroom::getId).collect(Collectors.toList()); + // 2. 逻辑删除这些教室下的所有设备 + deviceMapper.delete(new LambdaQueryWrapper().in(Device::getClassroomId, classroomIds)); + // 3. 逻辑删除教室 + classroomMapper.deleteBatchIds(classroomIds); + } + // 4. 逻辑删除教学楼 + return super.removeByIds(ids); + } +} diff --git a/src/main/java/com/attendance/service/impl/ClassroomServiceImpl.java b/src/main/java/com/attendance/service/impl/ClassroomServiceImpl.java new file mode 100644 index 0000000..766ed15 --- /dev/null +++ b/src/main/java/com/attendance/service/impl/ClassroomServiceImpl.java @@ -0,0 +1,140 @@ +package com.attendance.service.impl; + +import com.attendance.entity.AttDetail; +import com.attendance.entity.AttTask; +import com.attendance.entity.Classroom; +import com.attendance.entity.Device; +import com.attendance.mapper.AttDetailMapper; +import com.attendance.mapper.AttTaskMapper; +import com.attendance.mapper.ClassroomMapper; +import com.attendance.mapper.DeviceMapper; +import com.attendance.service.ClassroomService; +import com.attendance.vo.ClassroomAttendanceVO; +import com.attendance.vo.ClassroomVO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class ClassroomServiceImpl extends ServiceImpl implements ClassroomService { + + private final DeviceMapper deviceMapper; + private final AttTaskMapper attTaskMapper; + private final AttDetailMapper attDetailMapper; + + public ClassroomServiceImpl(DeviceMapper deviceMapper, + AttTaskMapper attTaskMapper, + AttDetailMapper attDetailMapper) { + this.deviceMapper = deviceMapper; + this.attTaskMapper = attTaskMapper; + this.attDetailMapper = attDetailMapper; + } + + @Override + public IPage getClassroomPage(IPage page, Long buildingId) { + return baseMapper.selectClassroomPage((Page) page, buildingId); + } + + @Override + public List getClassroomList(Long buildingId) { + return baseMapper.selectClassroomList(buildingId); + } + + @Override + public ClassroomAttendanceVO getCurrentAttendance(Long classroomId, LocalDateTime currentTime) { + LocalDate attDate = currentTime.toLocalDate(); + // 查询当天的考勤任务,按开始时间筛选匹配当前时间的任务 + List tasks = attTaskMapper.selectList( + new LambdaQueryWrapper() + .eq(AttTask::getClassroomId, classroomId) + .eq(AttTask::getAttDate, attDate) + .le(AttTask::getStartTime, currentTime) + .ge(AttTask::getEndTime, currentTime)); + if (tasks.isEmpty()) { + return null; + } + AttTask task = tasks.get(0); + + // 查询该任务的签到明细 + List details = attDetailMapper.selectByTaskId(task.getId()); + + // 统计应到、已到(正常+迟到)、出勤率 + int total = details.size(); + long actual = details.stream() + .filter(d -> d.getAttStatus() != null && (d.getAttStatus() == 1 || d.getAttStatus() == 2)) + .count(); + BigDecimal rate = total > 0 + ? BigDecimal.valueOf(actual).multiply(BigDecimal.valueOf(100)).divide(BigDecimal.valueOf(total), 2, RoundingMode.HALF_UP) + : BigDecimal.ZERO; + + // 构建响应 + ClassroomAttendanceVO vo = new ClassroomAttendanceVO(); + vo.setTaskId(task.getId()); + vo.setCourseId(task.getCourseId()); + vo.setCourseName(task.getCourseName()); + vo.setTeacherId(task.getTeacherId()); + vo.setTeacherName(task.getTeacherName()); + vo.setClassroomName(task.getClassroomName()); + vo.setClassId(task.getClassId()); + vo.setAttDate(task.getAttDate()); + vo.setStartTime(task.getStartTime()); + vo.setEndTime(task.getEndTime()); + vo.setTotalCount(total); + vo.setActualCount((int) actual); + vo.setAttendanceRate(rate); + vo.setTaskStatus(task.getTaskStatus()); + + List detailList = details.stream().map(d -> { + ClassroomAttendanceVO.AttendeeDetail ad = new ClassroomAttendanceVO.AttendeeDetail(); + ad.setId(d.getId()); + ad.setStudentId(d.getStudentId()); + ad.setStudentNo(d.getStudentNo()); + ad.setStudentName(d.getStudentName()); + ad.setCheckInTime(d.getCheckInTime()); + ad.setAttStatus(d.getAttStatus()); + ad.setAttStatusDesc(getAttStatusDesc(d.getAttStatus())); + ad.setCheckType(d.getCheckType()); + ad.setFaceSimilarity(d.getFaceSimilarity()); + ad.setFaceImage(d.getFaceImage()); + return ad; + }).collect(Collectors.toList()); + vo.setDetailList(detailList); + + return vo; + } + + private String getAttStatusDesc(Integer status) { + if (status == null) return "未知"; + switch (status) { + case 0: return "未签到"; + case 1: return "正常"; + case 2: return "迟到"; + case 3: return "缺勤"; + case 4: return "早退"; + case 5: return "请假"; + default: return "未知"; + } + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean removeByIds(List ids) { + if (ids == null || ids.isEmpty()) { + return false; + } + // 1. 逻辑删除这些教室下的所有设备 + deviceMapper.delete(new LambdaQueryWrapper().in(Device::getClassroomId, ids)); + // 2. 逻辑删除教室 + return super.removeByIds(ids); + } +} diff --git a/src/main/java/com/attendance/service/impl/CourseScheduleServiceImpl.java b/src/main/java/com/attendance/service/impl/CourseScheduleServiceImpl.java new file mode 100644 index 0000000..2b0a44f --- /dev/null +++ b/src/main/java/com/attendance/service/impl/CourseScheduleServiceImpl.java @@ -0,0 +1,235 @@ +package com.attendance.service.impl; + +import com.attendance.entity.*; +import com.attendance.mapper.CourseScheduleMapper; +import com.attendance.service.*; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class CourseScheduleServiceImpl extends ServiceImpl implements CourseScheduleService { + + private final AttTaskService attTaskService; + private final AttDetailService attDetailService; + private final CourseService courseService; + private final ClassroomService classroomService; + private final TeacherService teacherService; + private final EduClassService eduClassService; + private final StudentService studentService; + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveWithTasks(CourseSchedule schedule) { + // 1. 保存课程安排 + save(schedule); + + // 2. 查询关联信息 + Course course = courseService.getById(schedule.getCourseId()); + Classroom classroom = classroomService.getById(schedule.getClassroomId()); + Teacher teacher = teacherService.getById(schedule.getTeacherId()); + EduClass eduClass = schedule.getClassId() != null ? eduClassService.getById(schedule.getClassId()) : null; + + // 3. 计算每节课的日期和时间,生成考勤任务 + LocalDateTime firstClassTime = schedule.getFirstClassTime(); + LocalDate firstDate = firstClassTime.toLocalDate(); + java.time.LocalTime classStartTime = firstClassTime.toLocalTime(); + + // 每节课时长:每节45分钟,课间休息10分钟,最后一节不计课间 + int sectionCount = schedule.getEndSection() - schedule.getStartSection() + 1; + long durationMinutes = sectionCount * 45L + (sectionCount - 1) * 10L; + + List tasks = new ArrayList<>(); + for (int week = schedule.getStartWeek(); week <= schedule.getEndWeek(); week++) { + // 计算该周的上课日期 + LocalDate attDate = firstDate.plusWeeks(week - schedule.getStartWeek()); + LocalDateTime startTime = LocalDateTime.of(attDate, classStartTime); + LocalDateTime endTime = startTime.plusMinutes(durationMinutes); + + AttTask task = new AttTask(); + task.setTaskNo(generateTaskNo(attDate)); + task.setScheduleId(schedule.getId()); + task.setCourseId(schedule.getCourseId()); + task.setCourseName(course != null ? course.getCourseName() : null); + task.setClassroomId(schedule.getClassroomId()); + task.setClassroomName(classroom != null ? classroom.getRoomName() : null); + task.setTeacherId(schedule.getTeacherId()); + task.setTeacherName(teacher != null ? teacher.getName() : null); + task.setClassId(schedule.getClassId()); + task.setAttDate(attDate); + task.setStartTime(startTime); + task.setEndTime(endTime); + task.setTotalCount(eduClass != null ? eduClass.getStudentCount() : 0); + task.setActualCount(0); + task.setAbsentCount(0); + task.setLateCount(0); + task.setLeaveEarlyCount(0); + task.setTaskStatus(0); + tasks.add(task); + } + + attTaskService.saveBatch(tasks); + + // 4. 根据班级ID查询学生,为每个任务生成考勤明细 + if (schedule.getClassId() != null) { + List students = studentService.list( + new LambdaQueryWrapper() + .eq(Student::getClassId, schedule.getClassId()) + .eq(Student::getStatus, 1) + ); + + List allDetails = new ArrayList<>(); + for (AttTask task : tasks) { + for (Student student : students) { + AttDetail detail = new AttDetail(); + detail.setTaskId(task.getId()); + detail.setScheduleId(schedule.getId()); + detail.setStudentId(student.getId()); + detail.setStudentNo(student.getStudentNo()); + detail.setStudentName(student.getName()); + detail.setCourseId(schedule.getCourseId()); + detail.setClassroomId(schedule.getClassroomId()); + detail.setAttDate(task.getAttDate()); + detail.setAttStatus(0); // 未签到 + allDetails.add(detail); + } + } + + if (!allDetails.isEmpty()) { + attDetailService.saveBatch(allDetails); + } + } + } + + private static final AtomicInteger SEQ = new AtomicInteger(0); + + private String generateTaskNo(LocalDate date) { + String dateStr = date.format(DateTimeFormatter.ofPattern("yyyyMMdd")); + int seq = SEQ.incrementAndGet() % 10000; + return "ATT" + dateStr + String.format("%04d", seq); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateWithTasks(CourseSchedule schedule) { + // 1. 删除旧的考勤任务和明细 + List oldTasks = attTaskService.list( + new LambdaQueryWrapper().eq(AttTask::getScheduleId, schedule.getId()) + ); + if (!oldTasks.isEmpty()) { + List oldTaskIds = oldTasks.stream().map(AttTask::getId).collect(Collectors.toList()); + attDetailService.remove( + new LambdaQueryWrapper().in(AttDetail::getTaskId, oldTaskIds) + ); + attTaskService.removeByIds(oldTaskIds); + } + + // 2. 更新课程安排 + updateById(schedule); + + // 3. 重新生成考勤任务和明细 + Course course = courseService.getById(schedule.getCourseId()); + Classroom classroom = classroomService.getById(schedule.getClassroomId()); + Teacher teacher = teacherService.getById(schedule.getTeacherId()); + EduClass eduClass = schedule.getClassId() != null ? eduClassService.getById(schedule.getClassId()) : null; + + LocalDateTime firstClassTime = schedule.getFirstClassTime(); + LocalDate firstDate = firstClassTime.toLocalDate(); + java.time.LocalTime classStartTime = firstClassTime.toLocalTime(); + + int sectionCount = schedule.getEndSection() - schedule.getStartSection() + 1; + long durationMinutes = sectionCount * 45L + (sectionCount - 1) * 10L; + + List tasks = new ArrayList<>(); + for (int week = schedule.getStartWeek(); week <= schedule.getEndWeek(); week++) { + LocalDate attDate = firstDate.plusWeeks(week - schedule.getStartWeek()); + LocalDateTime startTime = LocalDateTime.of(attDate, classStartTime); + LocalDateTime endTime = startTime.plusMinutes(durationMinutes); + + AttTask task = new AttTask(); + task.setTaskNo(generateTaskNo(attDate)); + task.setScheduleId(schedule.getId()); + task.setCourseId(schedule.getCourseId()); + task.setCourseName(course != null ? course.getCourseName() : null); + task.setClassroomId(schedule.getClassroomId()); + task.setClassroomName(classroom != null ? classroom.getRoomName() : null); + task.setTeacherId(schedule.getTeacherId()); + task.setTeacherName(teacher != null ? teacher.getName() : null); + task.setClassId(schedule.getClassId()); + task.setAttDate(attDate); + task.setStartTime(startTime); + task.setEndTime(endTime); + task.setTotalCount(eduClass != null ? eduClass.getStudentCount() : 0); + task.setActualCount(0); + task.setAbsentCount(0); + task.setLateCount(0); + task.setLeaveEarlyCount(0); + task.setTaskStatus(0); + tasks.add(task); + } + + attTaskService.saveBatch(tasks); + + // 4. 重新生成考勤明细 + if (schedule.getClassId() != null) { + List students = studentService.list( + new LambdaQueryWrapper() + .eq(Student::getClassId, schedule.getClassId()) + .eq(Student::getStatus, 1) + ); + + List allDetails = new ArrayList<>(); + for (AttTask task : tasks) { + for (Student student : students) { + AttDetail detail = new AttDetail(); + detail.setTaskId(task.getId()); + detail.setScheduleId(schedule.getId()); + detail.setStudentId(student.getId()); + detail.setStudentNo(student.getStudentNo()); + detail.setStudentName(student.getName()); + detail.setCourseId(schedule.getCourseId()); + detail.setClassroomId(schedule.getClassroomId()); + detail.setAttDate(task.getAttDate()); + detail.setAttStatus(0); + allDetails.add(detail); + } + } + + if (!allDetails.isEmpty()) { + attDetailService.saveBatch(allDetails); + } + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void removeWithTasks(List ids) { + // 1. 查询所有关联的考勤任务 + List tasks = attTaskService.list( + new LambdaQueryWrapper().in(AttTask::getScheduleId, ids) + ); + if (!tasks.isEmpty()) { + List taskIds = tasks.stream().map(AttTask::getId).collect(Collectors.toList()); + // 2. 删除考勤明细 + attDetailService.remove( + new LambdaQueryWrapper().in(AttDetail::getTaskId, taskIds) + ); + // 3. 删除考勤任务 + attTaskService.removeByIds(taskIds); + } + + // 4. 删除课程安排 + removeByIds(ids); + } +} diff --git a/src/main/java/com/attendance/service/impl/CourseServiceImpl.java b/src/main/java/com/attendance/service/impl/CourseServiceImpl.java new file mode 100644 index 0000000..c565c8f --- /dev/null +++ b/src/main/java/com/attendance/service/impl/CourseServiceImpl.java @@ -0,0 +1,19 @@ +package com.attendance.service.impl; + +import com.attendance.entity.Course; +import com.attendance.mapper.CourseMapper; +import com.attendance.service.CourseService; +import com.attendance.vo.CourseVO; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +@Service +public class CourseServiceImpl extends ServiceImpl implements CourseService { + + @Override + public IPage getCoursePage(IPage page, String courseName, String teacherName, String courseType) { + return baseMapper.selectCoursePage((Page) page, courseName, teacherName, courseType); + } +} diff --git a/src/main/java/com/attendance/service/impl/DeviceServiceImpl.java b/src/main/java/com/attendance/service/impl/DeviceServiceImpl.java new file mode 100644 index 0000000..55ab6f0 --- /dev/null +++ b/src/main/java/com/attendance/service/impl/DeviceServiceImpl.java @@ -0,0 +1,11 @@ +package com.attendance.service.impl; + +import com.attendance.entity.Device; +import com.attendance.mapper.DeviceMapper; +import com.attendance.service.DeviceService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +@Service +public class DeviceServiceImpl extends ServiceImpl implements DeviceService { +} diff --git a/src/main/java/com/attendance/service/impl/EduClassServiceImpl.java b/src/main/java/com/attendance/service/impl/EduClassServiceImpl.java new file mode 100644 index 0000000..1c207dd --- /dev/null +++ b/src/main/java/com/attendance/service/impl/EduClassServiceImpl.java @@ -0,0 +1,11 @@ +package com.attendance.service.impl; + +import com.attendance.entity.EduClass; +import com.attendance.mapper.EduClassMapper; +import com.attendance.service.EduClassService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +@Service +public class EduClassServiceImpl extends ServiceImpl implements EduClassService { +} diff --git a/src/main/java/com/attendance/service/impl/FaceIdentityServiceImpl.java b/src/main/java/com/attendance/service/impl/FaceIdentityServiceImpl.java new file mode 100644 index 0000000..9098541 --- /dev/null +++ b/src/main/java/com/attendance/service/impl/FaceIdentityServiceImpl.java @@ -0,0 +1,11 @@ +package com.attendance.service.impl; + +import com.attendance.entity.FaceIdentity; +import com.attendance.mapper.FaceIdentityMapper; +import com.attendance.service.FaceIdentityService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +@Service +public class FaceIdentityServiceImpl extends ServiceImpl implements FaceIdentityService { +} diff --git a/src/main/java/com/attendance/service/impl/FaceSampleServiceImpl.java b/src/main/java/com/attendance/service/impl/FaceSampleServiceImpl.java new file mode 100644 index 0000000..3b6face --- /dev/null +++ b/src/main/java/com/attendance/service/impl/FaceSampleServiceImpl.java @@ -0,0 +1,16 @@ +package com.attendance.service.impl; + +import com.attendance.entity.FaceSample; +import com.attendance.mapper.FaceSampleMapper; +import com.attendance.service.FaceSampleService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +@Service +public class FaceSampleServiceImpl extends ServiceImpl implements FaceSampleService { + + @Override + public void addFaceSample(FaceSample faceSample) { + save(faceSample); + } +} diff --git a/src/main/java/com/attendance/service/impl/MonitorRecordServiceImpl.java b/src/main/java/com/attendance/service/impl/MonitorRecordServiceImpl.java new file mode 100644 index 0000000..c219f19 --- /dev/null +++ b/src/main/java/com/attendance/service/impl/MonitorRecordServiceImpl.java @@ -0,0 +1,11 @@ +package com.attendance.service.impl; + +import com.attendance.entity.MonitorRecord; +import com.attendance.mapper.MonitorRecordMapper; +import com.attendance.service.MonitorRecordService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +@Service +public class MonitorRecordServiceImpl extends ServiceImpl implements MonitorRecordService { +} diff --git a/src/main/java/com/attendance/service/impl/StudentServiceImpl.java b/src/main/java/com/attendance/service/impl/StudentServiceImpl.java new file mode 100644 index 0000000..a856b74 --- /dev/null +++ b/src/main/java/com/attendance/service/impl/StudentServiceImpl.java @@ -0,0 +1,169 @@ +package com.attendance.service.impl; + +import com.attendance.dto.StudentDTO; +import com.attendance.entity.FaceSample; +import com.attendance.entity.Student; +import com.attendance.mapper.StudentMapper; +import com.attendance.service.FaceIdentityService; +import com.attendance.service.FaceSampleService; +import com.attendance.service.StudentService; +import com.attendance.utils.MinioUtil; +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 com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +public class StudentServiceImpl extends ServiceImpl implements StudentService { + + private final MinioUtil minioUtil; + private final FaceSampleService faceSampleService; + private final FaceIdentityService faceIdentityService; + + @Value("${nginx.upload-path}") + private String nginxUploadPath; + + @Value("${nginx.access-url-prefix}") + private String nginxAccessUrlPrefix; + + public StudentServiceImpl(MinioUtil minioUtil, FaceSampleService faceSampleService, FaceIdentityService faceIdentityService) { + this.minioUtil = minioUtil; + this.faceSampleService = faceSampleService; + this.faceIdentityService = faceIdentityService; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void addStudentWithFaceSamples(StudentDTO dto) throws Exception { + // 1. 构建学生信息并保存获取ID + Student student = new Student(); + student.setStudentNo(dto.getStudentNo()); + student.setName(dto.getName()); + student.setGender(dto.getGender()); + student.setAvatar(dto.getAvatar()); + student.setClassId(dto.getClassId()); + student.setSchoolId(dto.getSchoolId()); + student.setPhone(dto.getPhone()); + student.setEmail(dto.getEmail()); + student.setUserId(dto.getUserId()); + student.setStatus(1); + save(student); + Integer studentId = student.getId().intValue(); + + // 2. 上传人脸图片到MinIO并保存记录 + uploadFaceImages(studentId, dto.getFrontImage(), "front"); + uploadFaceImages(studentId, dto.getLeftImage(), "left"); + uploadFaceImages(studentId, dto.getRightImage(), "right"); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void removeStudentsWithFaceData(List ids) { + // 1. 逻辑删除学生 + removeByIds(ids); + + // 2. 逻辑删除face_samples中关联的数据 + List studentIds = ids.stream().map(Long::intValue).collect(Collectors.toList()); + LambdaQueryWrapper sampleWrapper = new LambdaQueryWrapper<>(); + sampleWrapper.in(FaceSample::getStudentId, studentIds); + faceSampleService.remove(sampleWrapper); + + // 3. 逻辑删除face_identities中关联的数据 + LambdaQueryWrapper identityWrapper = new LambdaQueryWrapper<>(); + identityWrapper.in(com.attendance.entity.FaceIdentity::getStudentId, studentIds); + faceIdentityService.remove(identityWrapper); + } + + @Override + public IPage getStudentPage(IPage page, Long classId, String keyword) { + return baseMapper.selectStudentPage((Page) page, classId, keyword); + } + + /** + * 上传人脸图片到MinIO并保存记录 + */ + private void uploadFaceImages(Integer studentId, List files, String sampleType) throws Exception { + if (files == null || files.isEmpty()) { + return; + } + for (MultipartFile file : files) { + if (file.isEmpty()) { + continue; + } + String originalFilename = file.getOriginalFilename(); + String suffix = originalFilename.substring(originalFilename.lastIndexOf(".") + 1); + + // 构建MinIO存储路径:studentId/sampleType/文件名 + String uniqueName = UUID.randomUUID() + "." + suffix; + String objectName = "attendance/student/" + studentId + "/" + sampleType + "/" + uniqueName; + + // 上传到MinIO + try (InputStream inputStream = file.getInputStream()) { + minioUtil.uploadFile(objectName, inputStream, file.getSize(), file.getContentType()); + } + + // 获取图片URL并保存到FaceSample表 + String imageUrl = minioUtil.getUrl(objectName); + + FaceSample faceSample = new FaceSample(); + faceSample.setStudentId(studentId); + faceSample.setSampleType(sampleType); + faceSample.setImagePath(imageUrl); + faceSample.setUploadTime(LocalDateTime.now()); + faceSampleService.addFaceSample(faceSample); + } + } + + /** + * 上传人脸图片到Nginx本地目录并保存记录 + */ + private void uploadFaceImagesToNginx(Integer studentId, List files, String sampleType) throws Exception { + if (files == null || files.isEmpty()) { + return; + } + for (MultipartFile file : files) { + if (file.isEmpty()) { + continue; + } + String originalFilename = file.getOriginalFilename(); + String suffix = originalFilename.substring(originalFilename.lastIndexOf(".") + 1); + + // 构建Nginx存储子路径:studentId/sampleType/文件名 + String uniqueName = UUID.randomUUID() + "." + suffix; + String relativePath = "attendance/student/" + studentId + "/" + sampleType + "/" + uniqueName; + + // 完整的目标文件路径 + Path targetPath = Paths.get(nginxUploadPath, relativePath); + + // 确保父目录存在 + Files.createDirectories(targetPath.getParent()); + + // 写入本地文件 + file.transferTo(targetPath.toFile()); + + // 构造访问URL(Nginx 代理前缀 + 相对路径) + String imageUrl = nginxAccessUrlPrefix + "/" + relativePath; + + FaceSample faceSample = new FaceSample(); + faceSample.setStudentId(studentId); + faceSample.setSampleType(sampleType); + faceSample.setImagePath(imageUrl); + faceSample.setUploadTime(LocalDateTime.now()); + faceSampleService.addFaceSample(faceSample); + } + } +} diff --git a/src/main/java/com/attendance/service/impl/SysOperationLogServiceImpl.java b/src/main/java/com/attendance/service/impl/SysOperationLogServiceImpl.java new file mode 100644 index 0000000..b0dcccb --- /dev/null +++ b/src/main/java/com/attendance/service/impl/SysOperationLogServiceImpl.java @@ -0,0 +1,25 @@ +package com.attendance.service.impl; + +import com.attendance.entity.SysOperationLog; +import com.attendance.mapper.SysOperationLogMapper; +import com.attendance.service.SysOperationLogService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class SysOperationLogServiceImpl extends ServiceImpl implements SysOperationLogService { + + @Override + @Async + public void asyncSave(SysOperationLog sysOperationLog) { + try { + save(sysOperationLog); + } catch (Exception e) { + log.error("异步保存操作日志失败: userId={}, module={}, action={}", + sysOperationLog.getUserId(), sysOperationLog.getModule(), sysOperationLog.getAction(), e); + } + } +} diff --git a/src/main/java/com/attendance/service/impl/SysPermissionServiceImpl.java b/src/main/java/com/attendance/service/impl/SysPermissionServiceImpl.java new file mode 100644 index 0000000..3b45115 --- /dev/null +++ b/src/main/java/com/attendance/service/impl/SysPermissionServiceImpl.java @@ -0,0 +1,22 @@ +package com.attendance.service.impl; + +import com.attendance.entity.SysPermission; +import com.attendance.mapper.SysPermissionMapper; +import com.attendance.service.SysPermissionService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class SysPermissionServiceImpl extends ServiceImpl implements SysPermissionService { + + private final SysPermissionMapper sysPermissionMapper; + + @Override + public List getPermissionsByRoleId(Long roleId) { + return sysPermissionMapper.selectByRoleId(roleId); + } +} diff --git a/src/main/java/com/attendance/service/impl/SysRoleServiceImpl.java b/src/main/java/com/attendance/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..50b6a7a --- /dev/null +++ b/src/main/java/com/attendance/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,11 @@ +package com.attendance.service.impl; + +import com.attendance.entity.SysRole; +import com.attendance.mapper.SysRoleMapper; +import com.attendance.service.SysRoleService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +@Service +public class SysRoleServiceImpl extends ServiceImpl implements SysRoleService { +} diff --git a/src/main/java/com/attendance/service/impl/SysSchoolServiceImpl.java b/src/main/java/com/attendance/service/impl/SysSchoolServiceImpl.java new file mode 100644 index 0000000..2663ea3 --- /dev/null +++ b/src/main/java/com/attendance/service/impl/SysSchoolServiceImpl.java @@ -0,0 +1,11 @@ +package com.attendance.service.impl; + +import com.attendance.entity.SysSchool; +import com.attendance.mapper.SysSchoolMapper; +import com.attendance.service.SysSchoolService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +@Service +public class SysSchoolServiceImpl extends ServiceImpl implements SysSchoolService { +} diff --git a/src/main/java/com/attendance/service/impl/SysUserServiceImpl.java b/src/main/java/com/attendance/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..ac8ece8 --- /dev/null +++ b/src/main/java/com/attendance/service/impl/SysUserServiceImpl.java @@ -0,0 +1,101 @@ +package com.attendance.service.impl; + +import com.attendance.common.BusinessException; +import com.attendance.common.ResultCode; +import com.attendance.dto.LoginDTO; +import com.attendance.dto.LoginResultDTO; +import com.attendance.dto.PasswordDTO; +import com.attendance.entity.SysRole; +import com.attendance.entity.SysSchool; +import com.attendance.entity.SysUser; +import com.attendance.mapper.SysRoleMapper; +import com.attendance.mapper.SysSchoolMapper; +import com.attendance.mapper.SysUserMapper; +import com.attendance.service.SysUserService; +import com.attendance.utils.JwtUtil; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import javax.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.DigestUtils; + +import java.nio.charset.StandardCharsets; + +@Slf4j +@Service +@RequiredArgsConstructor +public class SysUserServiceImpl extends ServiceImpl implements SysUserService { + + private final SysUserMapper sysUserMapper; + private final SysRoleMapper sysRoleMapper; + private final SysSchoolMapper sysSchoolMapper; + private final JwtUtil jwtUtil; + private final HttpServletRequest request; + + @Override + public LoginResultDTO login(LoginDTO loginDTO) { + SysUser user = sysUserMapper.selectByUsername(loginDTO.getUsername()); + if (user == null) { + throw new BusinessException(ResultCode.USER_NOT_FOUND); + } + if (user.getStatus() != 1) { + throw new BusinessException(ResultCode.USER_DISABLED); + } + + String encryptPassword = DigestUtils.md5DigestAsHex(loginDTO.getPassword().getBytes(StandardCharsets.UTF_8)); + if (!encryptPassword.equals(user.getPassword())) { + throw new BusinessException(ResultCode.USER_PASSWORD_ERROR); + } + + // 更新登录信息 + user.setLastLoginTime(java.time.LocalDateTime.now()); + user.setLastLoginIp(getClientIp()); + sysUserMapper.updateById(user); + + SysRole role = sysRoleMapper.selectById(user.getRoleId()); + SysSchool school = sysSchoolMapper.selectById(user.getSchoolId()); + + String token = jwtUtil.generateToken(user.getId(), user.getUsername(), role != null ? role.getRoleCode() : ""); + + LoginResultDTO result = new LoginResultDTO(); + result.setToken(token); + result.setTokenType("Bearer"); + result.setUserId(user.getId()); + result.setUsername(user.getUsername()); + result.setRealName(user.getRealName()); + result.setAvatar(user.getAvatar()); + result.setRole(role != null ? role.getRoleCode() : ""); + result.setRoleName(role != null ? role.getRoleName() : ""); + result.setSchoolName(school != null ? school.getName() : ""); + return result; + } + + @Override + public SysUser getByUsername(String username) { + return sysUserMapper.selectByUsername(username); + } + + @Override + public void updatePassword(Long userId, PasswordDTO passwordDTO) { + SysUser user = getById(userId); + if (user == null) { + throw new BusinessException(ResultCode.USER_NOT_FOUND); + } + String oldEncrypt = DigestUtils.md5DigestAsHex(passwordDTO.getOldPassword().getBytes(StandardCharsets.UTF_8)); + if (!oldEncrypt.equals(user.getPassword())) { + throw new BusinessException(ResultCode.USER_PASSWORD_ERROR); + } + String newEncrypt = DigestUtils.md5DigestAsHex(passwordDTO.getNewPassword().getBytes(StandardCharsets.UTF_8)); + user.setPassword(newEncrypt); + updateById(user); + } + + private String getClientIp() { + String ip = request.getHeader("X-Forwarded-For"); + if (ip == null || ip.isEmpty()) { + ip = request.getRemoteAddr(); + } + return ip.split(",")[0].trim(); + } +} diff --git a/src/main/java/com/attendance/service/impl/TeacherServiceImpl.java b/src/main/java/com/attendance/service/impl/TeacherServiceImpl.java new file mode 100644 index 0000000..0c768cb --- /dev/null +++ b/src/main/java/com/attendance/service/impl/TeacherServiceImpl.java @@ -0,0 +1,11 @@ +package com.attendance.service.impl; + +import com.attendance.entity.Teacher; +import com.attendance.mapper.TeacherMapper; +import com.attendance.service.TeacherService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +@Service +public class TeacherServiceImpl extends ServiceImpl implements TeacherService { +} diff --git a/src/main/java/com/attendance/utils/JwtUtil.java b/src/main/java/com/attendance/utils/JwtUtil.java new file mode 100644 index 0000000..71657cd --- /dev/null +++ b/src/main/java/com/attendance/utils/JwtUtil.java @@ -0,0 +1,54 @@ +package com.attendance.utils; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.DecodedJWT; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Date; + +@Component +public class JwtUtil { + + @Value("${jwt.secret}") + private String secret; + + @Value("${jwt.expire}") + private Long expire; + + public String generateToken(Long userId, String username, String role) { + Date now = new Date(); + Date expireDate = new Date(now.getTime() + expire); + + return JWT.create() + .withSubject(String.valueOf(userId)) + .withClaim("userId", userId) + .withClaim("username", username) + .withClaim("role", role) + .withIssuedAt(now) + .withExpiresAt(expireDate) + .sign(Algorithm.HMAC256(secret)); + } + + public DecodedJWT verifyToken(String token) { + JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build(); + return verifier.verify(token); + } + + public Long getUserId(String token) { + DecodedJWT jwt = JWT.decode(token); + return jwt.getClaim("userId").asLong(); + } + + public String getUsername(String token) { + DecodedJWT jwt = JWT.decode(token); + return jwt.getClaim("username").asString(); + } + + public String getRole(String token) { + DecodedJWT jwt = JWT.decode(token); + return jwt.getClaim("role").asString(); + } +} diff --git a/src/main/java/com/attendance/utils/MinioUtil.java b/src/main/java/com/attendance/utils/MinioUtil.java new file mode 100644 index 0000000..8b79514 --- /dev/null +++ b/src/main/java/com/attendance/utils/MinioUtil.java @@ -0,0 +1,91 @@ +package com.attendance.utils; + +import io.minio.*; +import io.minio.http.Method; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.io.InputStream; + +@Data +@Component +@ConfigurationProperties(prefix = "minio") +public class MinioUtil { + + private String endpoint; + private String accessKey; + private String secretKey; + private String bucketName; + + private MinioClient minioClient; + + public MinioClient getClient() { + if (minioClient == null) { + minioClient = MinioClient.builder() + .endpoint(endpoint) + .credentials(accessKey, secretKey) + .build(); + } + return minioClient; + } + + /** + * 确保Bucket存在,不存在则创建 + */ + public void ensureBucket() throws Exception { + MinioClient client = getClient(); + boolean found = client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); + if (!found) { + client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); + } + } + + /** + * 上传文件 + */ + public void uploadFile(String objectName, InputStream inputStream, long size, String contentType) throws Exception { + ensureBucket(); + getClient().putObject( + PutObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .stream(inputStream, size, -1) + .contentType(contentType) + .build() + ); + } + + /** + * 获取文件访问URL + */ + public String getUrl(String objectName) { + return "/" + bucketName + "/" + objectName; + } + + /** + * 获取预签名下载URL(有效期7天) + */ + public String getPresignedUrl(String objectName) throws Exception { + return getClient().getPresignedObjectUrl( + GetPresignedObjectUrlArgs.builder() + .method(Method.GET) + .bucket(bucketName) + .object(objectName) + .expiry(7 * 24 * 3600) + .build() + ); + } + + /** + * 删除文件 + */ + public void deleteFile(String objectName) throws Exception { + getClient().removeObject( + RemoveObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .build() + ); + } +} diff --git a/src/main/java/com/attendance/vo/AttRecordVO.java b/src/main/java/com/attendance/vo/AttRecordVO.java new file mode 100644 index 0000000..10cf60b --- /dev/null +++ b/src/main/java/com/attendance/vo/AttRecordVO.java @@ -0,0 +1,85 @@ +package com.attendance.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +@Schema(description = "考勤记录VO") +public class AttRecordVO { + + @Schema(description = "主键ID") + private Long id; + + @Schema(description = "考勤任务ID") + private Long taskId; + + @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 classId; + + @Schema(description = "班级名称") + private String className; + + @Schema(description = "教师ID") + private Long teacherId; + + @Schema(description = "教师名称") + private String teacherName; + + @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; +} diff --git a/src/main/java/com/attendance/vo/AttTaskVO.java b/src/main/java/com/attendance/vo/AttTaskVO.java new file mode 100644 index 0000000..1535c45 --- /dev/null +++ b/src/main/java/com/attendance/vo/AttTaskVO.java @@ -0,0 +1,82 @@ +package com.attendance.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +@Schema(description = "考勤任务VO(含统计信息)") +public class AttTaskVO { + + @Schema(description = "任务ID") + private Long id; + + @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 Integer leaveCount; + + @Schema(description = "出勤率%") + private BigDecimal attendanceRate; + + @Schema(description = "缺勤率%") + private BigDecimal absentRate; + + @Schema(description = "任务状态:0-未开始 1-进行中 2-已结束 3-已取消") + private Integer taskStatus; +} \ No newline at end of file diff --git a/src/main/java/com/attendance/vo/AttTrendVO.java b/src/main/java/com/attendance/vo/AttTrendVO.java new file mode 100644 index 0000000..f94b81f --- /dev/null +++ b/src/main/java/com/attendance/vo/AttTrendVO.java @@ -0,0 +1,23 @@ +package com.attendance.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +@Schema(description = "出勤趋势VO") +public class AttTrendVO { + + @Schema(description = "日期") + private String date; + + @Schema(description = "考勤总人次") + private Integer totalCount; + + @Schema(description = "正常出勤人次") + private Integer actualCount; + + @Schema(description = "出勤率%") + private BigDecimal attendanceRate; +} \ No newline at end of file diff --git a/src/main/java/com/attendance/vo/BehaviorRecordVO.java b/src/main/java/com/attendance/vo/BehaviorRecordVO.java new file mode 100644 index 0000000..3042806 --- /dev/null +++ b/src/main/java/com/attendance/vo/BehaviorRecordVO.java @@ -0,0 +1,78 @@ +package com.attendance.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@Schema(description = "课堂行为记录视图对象") +public class BehaviorRecordVO { + + @Schema(description = "记录ID") + private Long id; + + @Schema(description = "考勤任务ID") + private Long taskId; + + @Schema(description = "课程ID") + private Long courseId; + + @Schema(description = "课程名称") + private String courseName; + + @Schema(description = "教室ID") + private Long classroomId; + + @Schema(description = "学生ID") + private Long studentId; + + @Schema(description = "学生姓名") + private String studentName; + + @Schema(description = "行为类型ID") + private Long behaviorTypeId; + + @Schema(description = "行为类型名称") + private String behaviorTypeName; + + @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 = "教师名称") + private String teacherName; + + @Schema(description = "所属学校ID") + private Long schoolId; +} diff --git a/src/main/java/com/attendance/vo/BuildingVO.java b/src/main/java/com/attendance/vo/BuildingVO.java new file mode 100644 index 0000000..1d22962 --- /dev/null +++ b/src/main/java/com/attendance/vo/BuildingVO.java @@ -0,0 +1,41 @@ +package com.attendance.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Schema(description = "教学楼视图对象") +public class BuildingVO { + + @Schema(description = "主键ID") + private Long id; + + @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; + + @Schema(description = "教室数量") + private Integer classroomCount; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Schema(description = "创建时间") + private LocalDateTime createdAt; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Schema(description = "更新时间") + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/attendance/vo/ClassRankVO.java b/src/main/java/com/attendance/vo/ClassRankVO.java new file mode 100644 index 0000000..6f7b636 --- /dev/null +++ b/src/main/java/com/attendance/vo/ClassRankVO.java @@ -0,0 +1,26 @@ +package com.attendance.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +@Schema(description = "班级出勤排名VO") +public class ClassRankVO { + + @Schema(description = "班级ID") + private Long classId; + + @Schema(description = "班级名称") + private String className; + + @Schema(description = "考勤总人次") + private Integer totalCount; + + @Schema(description = "正常出勤人次") + private Integer actualCount; + + @Schema(description = "出勤率%") + private BigDecimal attendanceRate; +} \ No newline at end of file diff --git a/src/main/java/com/attendance/vo/ClassroomAttendanceVO.java b/src/main/java/com/attendance/vo/ClassroomAttendanceVO.java new file mode 100644 index 0000000..62423b0 --- /dev/null +++ b/src/main/java/com/attendance/vo/ClassroomAttendanceVO.java @@ -0,0 +1,98 @@ +package com.attendance.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +@Data +@Schema(description = "教室当前考勤信息") +public class ClassroomAttendanceVO { + + @Schema(description = "考勤任务ID") + private Long taskId; + + @Schema(description = "课程ID") + private Long courseId; + + @Schema(description = "课程名称") + private String courseName; + + @Schema(description = "教师ID") + private Long teacherId; + + @Schema(description = "教师姓名") + private String teacherName; + + @Schema(description = "教室名称") + private String classroomName; + + @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 BigDecimal attendanceRate; + + @Schema(description = "任务状态:0-未开始 1-进行中 2-已结束") + private Integer taskStatus; + + @Schema(description = "学生签到列表") + private List detailList; + + @Data + @Schema(description = "学生签到明细") + public static class AttendeeDetail { + + @Schema(description = "明细ID") + private Long id; + + @Schema(description = "学生ID") + private Long studentId; + + @Schema(description = "学号") + private String studentNo; + + @Schema(description = "学生姓名") + private String studentName; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Schema(description = "签到时间") + private LocalDateTime checkInTime; + + @Schema(description = "考勤状态:0-未签到 1-正常 2-迟到 3-缺勤 4-早退 5-请假") + private Integer attStatus; + + @Schema(description = "考勤状态描述") + private String attStatusDesc; + + @Schema(description = "签到方式:1-人脸识别 2-手动补签") + private Integer checkType; + + @Schema(description = "人脸相似度") + private BigDecimal faceSimilarity; + + @Schema(description = "抓拍人脸图片URL") + private String faceImage; + } +} diff --git a/src/main/java/com/attendance/vo/ClassroomVO.java b/src/main/java/com/attendance/vo/ClassroomVO.java new file mode 100644 index 0000000..1d42f12 --- /dev/null +++ b/src/main/java/com/attendance/vo/ClassroomVO.java @@ -0,0 +1,53 @@ +package com.attendance.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Schema(description = "教室视图对象") +public class ClassroomVO { + + @Schema(description = "主键ID") + private Long id; + + @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; + + @Schema(description = "设备数量") + private Integer deviceCount; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Schema(description = "创建时间") + private LocalDateTime createdAt; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Schema(description = "更新时间") + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/attendance/vo/CourseVO.java b/src/main/java/com/attendance/vo/CourseVO.java new file mode 100644 index 0000000..fe08a5a --- /dev/null +++ b/src/main/java/com/attendance/vo/CourseVO.java @@ -0,0 +1,51 @@ +package com.attendance.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@Schema(description = "课程视图对象") +public class CourseVO { + + @Schema(description = "主键ID") + private Long id; + + @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 = "教师名称") + private String teacherName; + + @Schema(description = "所属学校ID") + private Long schoolId; + + @Schema(description = "课程描述") + private String description; + + @Schema(description = "状态") + private Integer status; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Schema(description = "创建时间") + private LocalDateTime createdAt; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Schema(description = "更新时间") + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/attendance/vo/StudentVO.java b/src/main/java/com/attendance/vo/StudentVO.java new file mode 100644 index 0000000..e73e81c --- /dev/null +++ b/src/main/java/com/attendance/vo/StudentVO.java @@ -0,0 +1,59 @@ +package com.attendance.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Schema(description = "学生VO") +public class StudentVO { + + @Schema(description = "主键ID") + private Long id; + + @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; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Schema(description = "创建时间") + private LocalDateTime createdAt; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Schema(description = "更新时间") + private LocalDateTime updatedAt; +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..4e5bf87 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,79 @@ +server: + port: 8088 + servlet: + context-path: /api + +spring: + application: + name: attendance-system-server + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/attendance_system?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false + username: root + password: 123456 + hikari: + minimum-idle: 5 + maximum-pool-size: 20 + connection-timeout: 30000 + idle-timeout: 600000 + max-lifetime: 1800000 + connection-test-query: SELECT 1 + servlet: + multipart: + max-file-size: 10MB + max-request-size: 50MB + +# MyBatis-Plus +mybatis-plus: + mapper-locations: classpath:/mapper/**/*.xml + type-aliases-package: com.attendance.entity + configuration: + map-underscore-to-camel-case: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + id-type: auto + logic-delete-field: deleted + logic-delete-value: 1 + logic-not-delete-value: 0 + +# JWT +jwt: + secret: attendance-system-secret-key-2024-smart-campus + expire: 86400000 + +# MinIO +minio: + endpoint: http://ngsk5.vpn:39000 + access-key: admin + secret-key: ngsk0809 + bucket-name: document + domainName: https://shipllm.ngsk.tech:7001/storage + +# Nginx 本地上传路径 +nginx: + upload-path: D:/nginx-1.28.0/html/dist + access-url-prefix: /attendance-faces + +stream: + import-face-url: http://10.23.22.97:8010/api/database/import-face-samples + +# Knife4j +knife4j: + enable: true + setting: + language: zh_cn + swagger-model-name: 实体类列表 + +# SpringDoc OpenAPI +springdoc: + swagger-ui: + path: /swagger-ui.html + tags-sorter: alpha + operations-sorter: alpha + api-docs: + path: /v3/api-docs + group-configs: + - group: 'default' + paths-to-match: '/**' + packages-to-scan: com.attendance.controller diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..e8ef559 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + + + ${LOG_HOME}/${APP_NAME}.log + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + ${LOG_HOME}/${APP_NAME}.%d{yyyy-MM-dd}.log + 30 + + + + + + ${LOG_HOME}/${APP_NAME}-error.log + + ERROR + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + ${LOG_HOME}/${APP_NAME}-error.%d{yyyy-MM-dd}.log + 30 + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/AttRecordMapper.xml b/src/main/resources/mapper/AttRecordMapper.xml new file mode 100644 index 0000000..20c28af --- /dev/null +++ b/src/main/resources/mapper/AttRecordMapper.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/src/main/resources/mapper/AttTaskMapper.xml b/src/main/resources/mapper/AttTaskMapper.xml new file mode 100644 index 0000000..01559d7 --- /dev/null +++ b/src/main/resources/mapper/AttTaskMapper.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/BuildingMapper.xml b/src/main/resources/mapper/BuildingMapper.xml new file mode 100644 index 0000000..04edac4 --- /dev/null +++ b/src/main/resources/mapper/BuildingMapper.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/src/main/resources/mapper/ClassroomMapper.xml b/src/main/resources/mapper/ClassroomMapper.xml new file mode 100644 index 0000000..4ae535b --- /dev/null +++ b/src/main/resources/mapper/ClassroomMapper.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/src/main/resources/mapper/CourseMapper.xml b/src/main/resources/mapper/CourseMapper.xml new file mode 100644 index 0000000..e3b4e9a --- /dev/null +++ b/src/main/resources/mapper/CourseMapper.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/src/main/resources/mapper/StudentMapper.xml b/src/main/resources/mapper/StudentMapper.xml new file mode 100644 index 0000000..dcd3fe9 --- /dev/null +++ b/src/main/resources/mapper/StudentMapper.xml @@ -0,0 +1,26 @@ + + + + + + +