抽取状态枚举,重构批量导入代码

main
魏灿斌 3 days ago
parent c9f092e060
commit b53e8d62df

3
.gitignore vendored

@ -37,3 +37,6 @@ build/
### Mac OS ###
.DS_Store
/.idea/
/.claude/
/deploy/dev/mysql/data/
/deploy/dev/redis/data/

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
{"mysqld_version_id":80402,"dd_version":80300,"sdi_version":80019,"dd_object_type":"Table","dd_object":{"name":"session_account_connect_attrs","mysql_version_id":80402,"created":20251120094414,"last_altered":20251120094414,"hidden":1,"options":"avg_row_length=0;key_block_size=0;keys_disabled=0;pack_record=1;server_p_s_table=1;stats_auto_recalc=0;stats_sample_pages=0;","columns":[{"name":"PROCESSLIST_ID","type":9,"is_nullable":false,"is_zerofill":false,"is_unsigned":true,"is_auto_increment":false,"is_virtual":false,"hidden":1,"ordinal_position":1,"char_length":20,"numeric_precision":20,"numeric_scale":0,"numeric_scale_null":false,"datetime_precision":0,"datetime_precision_null":1,"has_no_default":true,"default_value_null":false,"srs_id_null":true,"srs_id":0,"default_value":"AAAAAAAAAAA=","default_value_utf8_null":true,"default_value_utf8":"","default_option":"","update_option":"","comment":"","generation_expression":"","generation_expression_utf8":"","options":"interval_count=0;","se_private_data":"","engine_attribute":"","secondary_engine_attribute":"","column_key":2,"column_type_utf8":"bigint unsigned","elements":[],"collation_id":46,"is_explicit_collation":false},{"name":"ATTR_NAME","type":16,"is_nullable":false,"is_zerofill":false,"is_unsigned":false,"is_auto_increment":false,"is_virtual":false,"hidden":1,"ordinal_position":2,"char_length":128,"numeric_precision":0,"numeric_scale":0,"numeric_scale_null":true,"datetime_precision":0,"datetime_precision_null":1,"has_no_default":true,"default_value_null":false,"srs_id_null":true,"srs_id":0,"default_value":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAA","default_value_utf8_null":true,"default_value_utf8":"","default_option":"","update_option":"","comment":"","generation_expression":"","generation_expression_utf8":"","options":"interval_count=0;","se_private_data":"","engine_attribute":"","secondary_engine_attribute":"","column_key":2,"column_type_utf8":"varchar(32)","elements":[],"collation_id":46,"is_explicit_collation":false},{"name":"ATTR_VALUE","type":16,"is_nullable":true,"is_zerofill":false,"is_unsigned":false,"is_auto_increment":false,"is_virtual":false,"hidden":1,"ordinal_position":3,"char_length":4096,"numeric_precision":0,"numeric_scale":0,"numeric_scale_null":true,"datetime_precision":0,"datetime_precision_null":1,"has_no_default":false,"default_value_null":true,"srs_id_null":true,"srs_id":0,"default_value":"","default_value_utf8_null":true,"default_value_utf8":"","default_option":"","update_option":"","comment":"","generation_expression":"","generation_expression_utf8":"","options":"interval_count=0;","se_private_data":"","engine_attribute":"","secondary_engine_attribute":"","column_key":1,"column_type_utf8":"varchar(1024)","elements":[],"collation_id":46,"is_explicit_collation":false},{"name":"ORDINAL_POSITION","type":4,"is_nullable":true,"is_zerofill":false,"is_unsigned":false,"is_auto_increment":false,"is_virtual":false,"hidden":1,"ordinal_position":4,"char_length":11,"numeric_precision":10,"numeric_scale":0,"numeric_scale_null":false,"datetime_precision":0,"datetime_precision_null":1,"has_no_default":false,"default_value_null":true,"srs_id_null":true,"srs_id":0,"default_value":"","default_value_utf8_null":true,"default_value_utf8":"","default_option":"","update_option":"","comment":"","generation_expression":"","generation_expression_utf8":"","options":"interval_count=0;","se_private_data":"","engine_attribute":"","secondary_engine_attribute":"","column_key":1,"column_type_utf8":"int","elements":[],"collation_id":46,"is_explicit_collation":false}],"schema_ref":"performance_schema","se_private_id":18446744073709551615,"engine":"PERFORMANCE_SCHEMA","last_checked_for_upgrade_version_id":0,"comment":"","se_private_data":"","engine_attribute":"","secondary_engine_attribute":"","row_format":2,"partition_type":0,"partition_expression":"","partition_expression_utf8":"","default_partitioning":0,"subpartition_type":0,"subpartition_expression":"","subpartition_expression_utf8":"","default_subpartitioning":0,"indexes":[{"name":"PRIMARY","hidden":false,"is_generated":false,"ordinal_position":1,"comment":"","options":"flags=0;","se_private_data":"","type":1,"algorithm":4,"is_algorithm_explicit":false,"is_visible":true,"engine":"PERFORMANCE_SCHEMA","engine_attribute":"","secondary_engine_attribute":"","elements":[{"ordinal_position":1,"length":8,"order":1,"hidden":false,"column_opx":0},{"ordinal_position":2,"length":128,"order":1,"hidden":false,"column_opx":1}]}],"foreign_keys":[],"check_constraints":[],"partitions":[],"collation_id":46}}

@ -0,0 +1 @@
{"mysqld_version_id":80402,"dd_version":80300,"sdi_version":80019,"dd_object_type":"Table","dd_object":{"name":"session_connect_attrs","mysql_version_id":80402,"created":20251120094414,"last_altered":20251120094414,"hidden":1,"options":"avg_row_length=0;key_block_size=0;keys_disabled=0;pack_record=1;server_p_s_table=1;stats_auto_recalc=0;stats_sample_pages=0;","columns":[{"name":"PROCESSLIST_ID","type":9,"is_nullable":false,"is_zerofill":false,"is_unsigned":true,"is_auto_increment":false,"is_virtual":false,"hidden":1,"ordinal_position":1,"char_length":20,"numeric_precision":20,"numeric_scale":0,"numeric_scale_null":false,"datetime_precision":0,"datetime_precision_null":1,"has_no_default":true,"default_value_null":false,"srs_id_null":true,"srs_id":0,"default_value":"AAAAAAAAAAA=","default_value_utf8_null":true,"default_value_utf8":"","default_option":"","update_option":"","comment":"","generation_expression":"","generation_expression_utf8":"","options":"interval_count=0;","se_private_data":"","engine_attribute":"","secondary_engine_attribute":"","column_key":2,"column_type_utf8":"bigint unsigned","elements":[],"collation_id":46,"is_explicit_collation":false},{"name":"ATTR_NAME","type":16,"is_nullable":false,"is_zerofill":false,"is_unsigned":false,"is_auto_increment":false,"is_virtual":false,"hidden":1,"ordinal_position":2,"char_length":128,"numeric_precision":0,"numeric_scale":0,"numeric_scale_null":true,"datetime_precision":0,"datetime_precision_null":1,"has_no_default":true,"default_value_null":false,"srs_id_null":true,"srs_id":0,"default_value":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAA","default_value_utf8_null":true,"default_value_utf8":"","default_option":"","update_option":"","comment":"","generation_expression":"","generation_expression_utf8":"","options":"interval_count=0;","se_private_data":"","engine_attribute":"","secondary_engine_attribute":"","column_key":2,"column_type_utf8":"varchar(32)","elements":[],"collation_id":46,"is_explicit_collation":false},{"name":"ATTR_VALUE","type":16,"is_nullable":true,"is_zerofill":false,"is_unsigned":false,"is_auto_increment":false,"is_virtual":false,"hidden":1,"ordinal_position":3,"char_length":4096,"numeric_precision":0,"numeric_scale":0,"numeric_scale_null":true,"datetime_precision":0,"datetime_precision_null":1,"has_no_default":false,"default_value_null":true,"srs_id_null":true,"srs_id":0,"default_value":"","default_value_utf8_null":true,"default_value_utf8":"","default_option":"","update_option":"","comment":"","generation_expression":"","generation_expression_utf8":"","options":"interval_count=0;","se_private_data":"","engine_attribute":"","secondary_engine_attribute":"","column_key":1,"column_type_utf8":"varchar(1024)","elements":[],"collation_id":46,"is_explicit_collation":false},{"name":"ORDINAL_POSITION","type":4,"is_nullable":true,"is_zerofill":false,"is_unsigned":false,"is_auto_increment":false,"is_virtual":false,"hidden":1,"ordinal_position":4,"char_length":11,"numeric_precision":10,"numeric_scale":0,"numeric_scale_null":false,"datetime_precision":0,"datetime_precision_null":1,"has_no_default":false,"default_value_null":true,"srs_id_null":true,"srs_id":0,"default_value":"","default_value_utf8_null":true,"default_value_utf8":"","default_option":"","update_option":"","comment":"","generation_expression":"","generation_expression_utf8":"","options":"interval_count=0;","se_private_data":"","engine_attribute":"","secondary_engine_attribute":"","column_key":1,"column_type_utf8":"int","elements":[],"collation_id":46,"is_explicit_collation":false}],"schema_ref":"performance_schema","se_private_id":18446744073709551615,"engine":"PERFORMANCE_SCHEMA","last_checked_for_upgrade_version_id":0,"comment":"","se_private_data":"","engine_attribute":"","secondary_engine_attribute":"","row_format":2,"partition_type":0,"partition_expression":"","partition_expression_utf8":"","default_partitioning":0,"subpartition_type":0,"subpartition_expression":"","subpartition_expression_utf8":"","default_subpartitioning":0,"indexes":[{"name":"PRIMARY","hidden":false,"is_generated":false,"ordinal_position":1,"comment":"","options":"flags=0;","se_private_data":"","type":1,"algorithm":4,"is_algorithm_explicit":false,"is_visible":true,"engine":"PERFORMANCE_SCHEMA","engine_attribute":"","secondary_engine_attribute":"","elements":[{"ordinal_position":1,"length":8,"order":1,"hidden":false,"column_opx":0},{"ordinal_position":2,"length":128,"order":1,"hidden":false,"column_opx":1}]}],"foreign_keys":[],"check_constraints":[],"partitions":[],"collation_id":46}}

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
{"mysqld_version_id":80402,"dd_version":80300,"sdi_version":80019,"dd_object_type":"Table","dd_object":{"name":"status_by_host","mysql_version_id":80402,"created":20251120094414,"last_altered":20251120094414,"hidden":1,"options":"avg_row_length=0;key_block_size=0;keys_disabled=0;pack_record=1;server_p_s_table=1;stats_auto_recalc=0;stats_sample_pages=0;","columns":[{"name":"HOST","type":29,"is_nullable":true,"is_zerofill":false,"is_unsigned":false,"is_auto_increment":false,"is_virtual":false,"hidden":1,"ordinal_position":1,"char_length":255,"numeric_precision":0,"numeric_scale":0,"numeric_scale_null":true,"datetime_precision":0,"datetime_precision_null":1,"has_no_default":false,"default_value_null":true,"srs_id_null":true,"srs_id":0,"default_value":"","default_value_utf8_null":true,"default_value_utf8":"","default_option":"","update_option":"","comment":"","generation_expression":"","generation_expression_utf8":"","options":"interval_count=0;","se_private_data":"","engine_attribute":"","secondary_engine_attribute":"","column_key":4,"column_type_utf8":"char(255)","elements":[],"collation_id":11,"is_explicit_collation":true},{"name":"VARIABLE_NAME","type":16,"is_nullable":false,"is_zerofill":false,"is_unsigned":false,"is_auto_increment":false,"is_virtual":false,"hidden":1,"ordinal_position":2,"char_length":256,"numeric_precision":0,"numeric_scale":0,"numeric_scale_null":true,"datetime_precision":0,"datetime_precision_null":1,"has_no_default":true,"default_value_null":false,"srs_id_null":true,"srs_id":0,"default_value":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","default_value_utf8_null":true,"default_value_utf8":"","default_option":"","update_option":"","comment":"","generation_expression":"","generation_expression_utf8":"","options":"interval_count=0;","se_private_data":"","engine_attribute":"","secondary_engine_attribute":"","column_key":1,"column_type_utf8":"varchar(64)","elements":[],"collation_id":255,"is_explicit_collation":false},{"name":"VARIABLE_VALUE","type":16,"is_nullable":true,"is_zerofill":false,"is_unsigned":false,"is_auto_increment":false,"is_virtual":false,"hidden":1,"ordinal_position":3,"char_length":4096,"numeric_precision":0,"numeric_scale":0,"numeric_scale_null":true,"datetime_precision":0,"datetime_precision_null":1,"has_no_default":false,"default_value_null":true,"srs_id_null":true,"srs_id":0,"default_value":"","default_value_utf8_null":true,"default_value_utf8":"","default_option":"","update_option":"","comment":"","generation_expression":"","generation_expression_utf8":"","options":"interval_count=0;","se_private_data":"","engine_attribute":"","secondary_engine_attribute":"","column_key":1,"column_type_utf8":"varchar(1024)","elements":[],"collation_id":255,"is_explicit_collation":false}],"schema_ref":"performance_schema","se_private_id":18446744073709551615,"engine":"PERFORMANCE_SCHEMA","last_checked_for_upgrade_version_id":0,"comment":"","se_private_data":"","engine_attribute":"","secondary_engine_attribute":"","row_format":2,"partition_type":0,"partition_expression":"","partition_expression_utf8":"","default_partitioning":0,"subpartition_type":0,"subpartition_expression":"","subpartition_expression_utf8":"","default_subpartitioning":0,"indexes":[{"name":"HOST","hidden":false,"is_generated":false,"ordinal_position":1,"comment":"","options":"flags=0;","se_private_data":"","type":2,"algorithm":4,"is_algorithm_explicit":false,"is_visible":true,"engine":"PERFORMANCE_SCHEMA","engine_attribute":"","secondary_engine_attribute":"","elements":[{"ordinal_position":1,"length":255,"order":1,"hidden":false,"column_opx":0},{"ordinal_position":2,"length":256,"order":1,"hidden":false,"column_opx":1}]}],"foreign_keys":[],"check_constraints":[],"partitions":[],"collation_id":255}}

Binary file not shown.

Binary file not shown.

@ -87,49 +87,3 @@ CREATE TABLE IF NOT EXISTS teachers (
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='教师表';
-- 课程表
CREATE TABLE IF NOT EXISTS courses (
id varchar(64) NOT NULL COMMENT '课程ID雪花算法生成',
teacher_id varchar(64) NOT NULL COMMENT '教师ID外键',
course_name VARCHAR(200) NOT NULL COMMENT '课程名称',
course_code VARCHAR(100) UNIQUE NOT NULL COMMENT '课程代码',
credit DECIMAL(3,1) COMMENT '学分',
total_hours INT COMMENT '总课时',
description TEXT COMMENT '课程描述',
status INT DEFAULT 1 COMMENT '状态1=启用0=禁用',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id),
FOREIGN KEY (teacher_id) REFERENCES teachers(id) ON DELETE RESTRICT COMMENT '教师外键约束'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程表';
-- 创建索引
CREATE INDEX idx_teacher_id ON courses(teacher_id);
CREATE INDEX idx_course_code ON courses(course_code);
CREATE INDEX idx_teacher_no ON teachers(teacher_no);
-- 课程成绩表
CREATE TABLE IF NOT EXISTS course_scores (
id varchar(64) NOT NULL COMMENT '成绩ID雪花算法生成',
course_id varchar(64) NOT NULL COMMENT '课程ID外键',
student_id varchar(64) NOT NULL COMMENT '学生ID外键',
usual_score DECIMAL(5,2) COMMENT '平时成绩(0-100)',
midterm_score DECIMAL(5,2) COMMENT '期中成绩(0-100)',
final_score DECIMAL(5,2) COMMENT '期末成绩(0-100)',
total_score DECIMAL(5,2) COMMENT '总成绩(0-100)',
grade VARCHAR(10) COMMENT '等级(A/B/C/D/F)',
status INT DEFAULT 1 COMMENT '状态1=正常0=删除',
remark VARCHAR(500) COMMENT '备注',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id),
FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE RESTRICT COMMENT '课程外键',
FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE RESTRICT COMMENT '学生外键',
UNIQUE KEY uk_course_student (course_id, student_id) COMMENT '同一学生同一课程唯一'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程成绩表';
-- 创建索引
CREATE INDEX idx_course_id ON course_scores(course_id);
CREATE INDEX idx_student_id ON course_scores(student_id);
CREATE INDEX idx_grade ON course_scores(grade);

Binary file not shown.

@ -0,0 +1,38 @@
package yuxingshi.sms.common.core.domain.enums;
/**
*
*
*/
public enum EntityStatus {
DISABLED(0, "禁用"),
ACTIVE(1, "启用");
private final int value;
private final String description;
EntityStatus(int value, String description) {
this.value = value;
this.description = description;
}
public int getValue() {
return value;
}
public String getDescription() {
return description;
}
/**
*
*/
public static EntityStatus fromValue(int value) {
for (EntityStatus status : EntityStatus.values()) {
if (status.getValue() == value) {
return status;
}
}
throw new IllegalArgumentException("未知的状态值: " + value);
}
}

@ -6,11 +6,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.excel.EasyExcel;
import yuxingshi.sms.server.domain.dto.stu_class.ClassExcelDTO;
import yuxingshi.sms.server.domain.dto.stu_class.ClassManageDTO;
import yuxingshi.sms.server.domain.dto.stu_class.*;
import yuxingshi.sms.server.service.ClassesService;
import yuxingshi.sms.server.domain.dto.stu_class.ClassQueryReqDTO;
import yuxingshi.sms.server.domain.dto.stu_class.ClassRespDTO;
import yuxingshi.sms.common.core.domain.dto.excel.ImportResultDTO;
import yuxingshi.sms.common.core.domain.dto.BasePageDTO;
import yuxingshi.sms.common.core.domain.vo.R;
@ -58,6 +55,16 @@ public class ClassesController {
return R.ok();
}
/**
*
*/
@GetMapping("/{classId}")
@RequireIdentity({UserRole.ROLE_SYS_ADMIN, UserRole.ROLE_ADMIN})
public R<ClassDetailDTO> getClassDetail(@PathVariable(value = "classId") String classId) {
ClassDetailDTO classDetail = classesService.getClassDetail(classId);
return R.ok(classDetail);
}
/**
*
*/

@ -1,83 +0,0 @@
package yuxingshi.sms.server.controller;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import yuxingshi.sms.server.service.CourseService;
import yuxingshi.sms.server.domain.dto.course.CourseManageDTO;
import yuxingshi.sms.server.domain.dto.course.CourseQueryReqDTO;
import yuxingshi.sms.server.domain.dto.course.CourseRespDTO;
import yuxingshi.sms.common.core.domain.dto.BasePageDTO;
import yuxingshi.sms.common.core.domain.vo.R;
import yuxingshi.sms.common.core.domain.enums.UserRole;
import yuxingshi.sms.common.intercepter.annotation.RequireIdentity;
@Slf4j
@RestController
@RequestMapping("/api/courses")
public class CourseController {
@Autowired
private CourseService courseService;
/**
*
*/
@PostMapping
@RequireIdentity({UserRole.ROLE_SYS_ADMIN, UserRole.ROLE_ADMIN, UserRole.ROLE_TEACHER})
public R<String> createCourse(@Valid @RequestBody CourseManageDTO dto) {
String courseId = courseService.createCourse(dto);
return R.ok(courseId, "课程创建成功");
}
/**
*
*/
@PutMapping("/{courseId}")
@RequireIdentity({UserRole.ROLE_SYS_ADMIN, UserRole.ROLE_ADMIN, UserRole.ROLE_TEACHER})
public R<Void> updateCourse(@PathVariable(value = "courseId") String courseId, @Valid @RequestBody CourseManageDTO dto) {
courseService.updateCourse(courseId, dto);
return R.ok();
}
/**
*
*/
@DeleteMapping("/{courseId}")
@RequireIdentity({UserRole.ROLE_SYS_ADMIN, UserRole.ROLE_ADMIN, UserRole.ROLE_TEACHER})
public R<Void> deleteCourse(@PathVariable(value = "courseId") String courseId) {
courseService.deleteCourse(courseId);
return R.ok();
}
/**
*
*/
@PutMapping("/{courseId}/disable")
@RequireIdentity({UserRole.ROLE_SYS_ADMIN, UserRole.ROLE_ADMIN, UserRole.ROLE_TEACHER})
public R<Void> disableCourse(@PathVariable(value = "courseId") String courseId) {
courseService.disableCourse(courseId);
return R.ok();
}
/**
*
*/
@PutMapping("/{courseId}/enable")
@RequireIdentity({UserRole.ROLE_SYS_ADMIN, UserRole.ROLE_ADMIN, UserRole.ROLE_TEACHER})
public R<Void> enableCourse(@PathVariable(value = "courseId") String courseId) {
courseService.enableCourse(courseId);
return R.ok();
}
/**
*
*/
@GetMapping
@RequireIdentity({UserRole.ROLE_SYS_ADMIN, UserRole.ROLE_ADMIN, UserRole.ROLE_TEACHER})
public R<BasePageDTO<CourseRespDTO>> queryCourses(@Valid CourseQueryReqDTO queryReq) {
BasePageDTO<CourseRespDTO> pageDTO = courseService.queryCourses(queryReq);
return R.ok(pageDTO);
}
}

@ -1,89 +0,0 @@
package yuxingshi.sms.server.controller;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import yuxingshi.sms.server.service.CourseScoreService;
import yuxingshi.sms.server.domain.dto.score.CourseScoreManageDTO;
import yuxingshi.sms.server.domain.dto.score.CourseScoreQueryReqDTO;
import yuxingshi.sms.server.domain.dto.score.CourseScoreRespDTO;
import yuxingshi.sms.common.core.domain.dto.BasePageDTO;
import yuxingshi.sms.common.core.domain.vo.R;
import yuxingshi.sms.common.core.domain.enums.UserRole;
import yuxingshi.sms.common.intercepter.annotation.RequireIdentity;
@Slf4j
@RestController
@RequestMapping("/api/course-scores")
public class CourseScoreController {
@Autowired
private CourseScoreService courseScoreService;
/**
*
*/
@PostMapping
@RequireIdentity({UserRole.ROLE_SYS_ADMIN, UserRole.ROLE_ADMIN, UserRole.ROLE_TEACHER})
public R<String> addCourseScore(@Valid @RequestBody CourseScoreManageDTO dto) {
String scoreId = courseScoreService.addCourseScore(dto);
return R.ok(scoreId, "成绩添加成功");
}
/**
*
*/
@PutMapping("/{scoreId}")
@RequireIdentity({UserRole.ROLE_SYS_ADMIN, UserRole.ROLE_ADMIN, UserRole.ROLE_TEACHER})
public R<Void> updateCourseScore(@PathVariable(value = "scoreId") String scoreId, @Valid @RequestBody CourseScoreManageDTO dto) {
courseScoreService.updateCourseScore(scoreId, dto);
return R.ok();
}
/**
*
*/
@DeleteMapping("/{scoreId}")
@RequireIdentity({UserRole.ROLE_SYS_ADMIN, UserRole.ROLE_ADMIN, UserRole.ROLE_TEACHER})
public R<Void> deleteCourseScore(@PathVariable(value = "scoreId") String scoreId) {
courseScoreService.deleteCourseScore(scoreId);
return R.ok();
}
/**
*
*/
@GetMapping
@RequireIdentity({UserRole.ROLE_SYS_ADMIN, UserRole.ROLE_ADMIN, UserRole.ROLE_TEACHER})
public R<BasePageDTO<CourseScoreRespDTO>> queryCourseScores(@Valid CourseScoreQueryReqDTO queryReq) {
BasePageDTO<CourseScoreRespDTO> pageDTO = courseScoreService.queryCourseScores(queryReq);
return R.ok(pageDTO);
}
/**
*
*/
@GetMapping("/student/{studentId}")
@RequireIdentity({UserRole.ROLE_SYS_ADMIN, UserRole.ROLE_ADMIN, UserRole.ROLE_TEACHER})
public R<BasePageDTO<CourseScoreRespDTO>> queryStudentScores(
@PathVariable(value = "studentId") String studentId,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
BasePageDTO<CourseScoreRespDTO> pageDTO = courseScoreService.queryStudentScores(studentId, pageNum, pageSize);
return R.ok(pageDTO);
}
/**
*
*/
@GetMapping("/course/{courseId}")
@RequireIdentity({UserRole.ROLE_SYS_ADMIN, UserRole.ROLE_ADMIN, UserRole.ROLE_TEACHER})
public R<BasePageDTO<CourseScoreRespDTO>> queryCourseStudentScores(
@PathVariable(value = "courseId") String courseId,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
BasePageDTO<CourseScoreRespDTO> pageDTO = courseScoreService.queryCourseStudentScores(courseId, pageNum, pageSize);
return R.ok(pageDTO);
}
}

@ -6,11 +6,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.excel.EasyExcel;
import yuxingshi.sms.server.domain.dto.stu.StudentExcelDTO;
import yuxingshi.sms.server.domain.dto.stu.*;
import yuxingshi.sms.server.service.StudentService;
import yuxingshi.sms.server.domain.dto.stu.StudentManageDTO;
import yuxingshi.sms.server.domain.dto.stu.StudentQueryReqDTO;
import yuxingshi.sms.server.domain.dto.stu.StudentRespDTO;
import yuxingshi.sms.common.core.domain.dto.excel.ImportResultDTO;
import yuxingshi.sms.common.core.domain.dto.BasePageDTO;
import yuxingshi.sms.common.core.domain.vo.R;
@ -99,4 +96,14 @@ public class StudentController {
ImportResultDTO result = studentService.importStudents(file);
return R.ok(result);
}
/**
*
*/
@GetMapping("/{studentId}")
public R<StudentDetailDTO> getStudentDetail(@PathVariable("studentId") String studentId) {
StudentDetailDTO studentDetail = studentService.getStudentDetail(studentId);
return R.ok(studentDetail);
}
}

@ -43,6 +43,15 @@ public class TeacherController {
return R.ok();
}
/**
*
*/
@GetMapping("/{teacherId}")
public R<TeacherRespDTO> getTeacherDetail(@PathVariable(value = "teacherId") String teacherId) {
TeacherRespDTO teacherRespDTO = teacherService.getTeacherDetail(teacherId);
return R.ok(teacherRespDTO);
}
/**
*
*/

@ -1,40 +0,0 @@
package yuxingshi.sms.server.domain.dto.course;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* /DTO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CourseManageDTO {
@NotBlank(message = "教师ID不能为空")
private String teacherId;
@NotBlank(message = "课程名称不能为空")
@Size(max = 100, message = "课程名称长度不能超过100个字符")
private String courseName;
@NotBlank(message = "课程代码不能为空")
@Pattern(regexp = "^[A-Z0-9]{4,20}$", message = "课程代码格式不正确应为4-20位大写字母或数字")
private String courseCode;
@DecimalMin(value = "0.5", message = "学分不能小于0.5")
@DecimalMax(value = "10.0", message = "学分不能大于10.0")
private BigDecimal credit;
@Min(value = 1, message = "总学时不能小于1")
@Max(value = 500, message = "总学时不能大于500")
private Integer totalHours;
@Size(max = 500, message = "课程描述长度不能超过500个字符")
private String description;
}

@ -1,24 +0,0 @@
package yuxingshi.sms.server.domain.dto.course;
import yuxingshi.sms.common.core.domain.dto.BasePageReqDTO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CourseQueryReqDTO extends BasePageReqDTO {
private String teacherId;
private String courseName;
private String courseCode;
private Integer status;
}

@ -1,41 +0,0 @@
package yuxingshi.sms.server.domain.dto.course;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* DTO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CourseRespDTO {
private String id;
private String teacherId;
private String courseName;
private String courseCode;
private BigDecimal credit;
private Integer totalHours;
private String description;
private Integer status;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updatedTime;
}

@ -1,40 +0,0 @@
package yuxingshi.sms.server.domain.dto.score;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* /DTO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CourseScoreManageDTO {
@NotBlank(message = "课程ID不能为空")
private String courseId;
@NotBlank(message = "学生ID不能为空")
private String studentId;
@DecimalMin(value = "0", message = "平时成绩不能小于0")
@DecimalMax(value = "100", message = "平时成绩不能大于100")
private BigDecimal usualScore;
@DecimalMin(value = "0", message = "期中成绩不能小于0")
@DecimalMax(value = "100", message = "期中成绩不能大于100")
private BigDecimal midtermScore;
@DecimalMin(value = "0", message = "期末成绩不能小于0")
@DecimalMax(value = "100", message = "期末成绩不能大于100")
private BigDecimal finalScore;
private String remark;
}

@ -1,24 +0,0 @@
package yuxingshi.sms.server.domain.dto.score;
import yuxingshi.sms.common.core.domain.dto.BasePageReqDTO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CourseScoreQueryReqDTO extends BasePageReqDTO {
private String courseId;
private String studentId;
private String grade;
private String sortBy; // 排序字段total_score, grade等
}

@ -1,51 +0,0 @@
package yuxingshi.sms.server.domain.dto.score;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* DTO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CourseScoreRespDTO {
private String id;
private String courseId;
private String courseName;
private String courseCode;
private String studentId;
private String studentNo;
private String studentName;
private BigDecimal usualScore;
private BigDecimal midtermScore;
private BigDecimal finalScore;
private BigDecimal totalScore;
private String grade;
private String remark;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updatedTime;
}

@ -0,0 +1,29 @@
package yuxingshi.sms.server.domain.dto.stu;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class StudentDetailDTO {
private String id;
private String userId;
private String classId;
private String classCode;
private String majorId;
private String studentNo;
private String realName;
private Integer gender;
private String phone;
}

@ -0,0 +1,25 @@
package yuxingshi.sms.server.domain.dto.stu_class;
import lombok.Builder;
import lombok.Data;
/**
* DTO
*/
@Builder
@Data
public class ClassDetailDTO {
private String id;
private String classCode;
private String majorId;
private String majorName;
private String majorCode;
private String description;
}

@ -17,8 +17,8 @@ public class ClassExcelDTO {
@ExcelProperty(index = 0, value = "班级代码")
private String classCode;
@ExcelProperty(index = 1, value = "专业ID")
private String majorId;
@ExcelProperty(index = 1, value = "专业代码")
private String majorCode;
@ExcelProperty(index = 2, value = "班级描述")
private String description;

@ -16,13 +16,13 @@ public class ClassReadListener extends ExcelImportUtil.BaseReadListener<ClassExc
return true;
}
// 如果所有主要字段都为空则跳过
if (StringUtils.isAllBlank(data.getClassCode(), data.getMajorId(), data.getDescription())) {
if (StringUtils.isAllBlank(data.getClassCode(), data.getMajorCode(), data.getDescription())) {
return true;
}
// 如果首行看起来像是表头,忽略这行
if (rowNo == 1) {
if ("班级代码".equalsIgnoreCase(data.getClassCode())
|| "专业ID".equalsIgnoreCase(data.getMajorId())) {
|| "专业代码".equalsIgnoreCase(data.getMajorCode())) {
return true;
}
}
@ -35,8 +35,8 @@ public class ClassReadListener extends ExcelImportUtil.BaseReadListener<ClassExc
if (data.getClassCode() != null) {
data.setClassCode(data.getClassCode().trim());
}
if (data.getMajorId() != null) {
data.setMajorId(data.getMajorId().trim());
if (data.getMajorCode() != null) {
data.setMajorCode(data.getMajorCode().trim());
}
if (data.getDescription() != null) {
data.setDescription(data.getDescription().trim());

@ -2,6 +2,8 @@ package yuxingshi.sms.server.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import yuxingshi.sms.server.domain.dto.stu_class.ClassDetailDTO;
import yuxingshi.sms.server.domain.entity.Classes;
/**
@ -9,4 +11,7 @@ import yuxingshi.sms.server.domain.entity.Classes;
*/
@Mapper
public interface ClassesMapper extends BaseMapper<Classes> {
@Select("select c.id,class_code,c.major_id,m.major_name,m.major_code,c.description from classes as c left join majors as m on c.major_id = m.id where c.id = #{id}")
ClassDetailDTO getClassWithMajorById(String id);
}

@ -1,12 +0,0 @@
package yuxingshi.sms.server.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import yuxingshi.sms.server.domain.entity.Course;
/**
* Mapper
*/
@Mapper
public interface CourseMapper extends BaseMapper<Course> {
}

@ -1,12 +0,0 @@
package yuxingshi.sms.server.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import yuxingshi.sms.server.domain.entity.CourseScore;
/**
* Mapper
*/
@Mapper
public interface CourseScoreMapper extends BaseMapper<CourseScore> {
}

@ -2,6 +2,8 @@ package yuxingshi.sms.server.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import yuxingshi.sms.server.domain.dto.stu.StudentDetailDTO;
import yuxingshi.sms.server.domain.entity.Student;
/**
@ -9,4 +11,7 @@ import yuxingshi.sms.server.domain.entity.Student;
*/
@Mapper
public interface StudentMapper extends BaseMapper<Student> {
@Select("select s.id,s.user_id,s.student_no,s.real_name,s.gender,s.phone,s.class_id, c.class_code from students as s left join classes as c on s.class_id = c.id where s.id = #{id}")
StudentDetailDTO getStudentAndClassDetail(String id);
}

@ -1,6 +1,7 @@
package yuxingshi.sms.server.service;
import org.springframework.web.multipart.MultipartFile;
import yuxingshi.sms.server.domain.dto.stu_class.ClassDetailDTO;
import yuxingshi.sms.server.domain.dto.stu_class.ClassManageDTO;
import yuxingshi.sms.server.domain.entity.Classes;
import yuxingshi.sms.server.domain.dto.stu_class.ClassQueryReqDTO;
@ -58,4 +59,9 @@ public interface ClassesService {
* @return
*/
ImportResultDTO importClasses(MultipartFile file);
/**
*
*/
ClassDetailDTO getClassDetail(String classId);
}

@ -1,62 +0,0 @@
package yuxingshi.sms.server.service;
import yuxingshi.sms.server.domain.entity.CourseScore;
import yuxingshi.sms.server.domain.dto.score.CourseScoreManageDTO;
import yuxingshi.sms.server.domain.dto.score.CourseScoreQueryReqDTO;
import yuxingshi.sms.server.domain.dto.score.CourseScoreRespDTO;
import yuxingshi.sms.common.core.domain.dto.BasePageDTO;
/**
*
*/
public interface CourseScoreService {
/**
*
*/
String addCourseScore(CourseScoreManageDTO dto);
/**
*
*/
void updateCourseScore(String scoreId, CourseScoreManageDTO dto);
/**
*
*/
void deleteCourseScore(String scoreId);
/**
* ID
*/
CourseScore getCourseScoreById(String scoreId);
/**
*
*/
CourseScore getCourseScore(String courseId, String studentId);
/**
*
*/
void deleteCourseAllScores(String courseId);
/**
*
*/
void deleteStudentAllScores(String studentId);
/**
*
*/
BasePageDTO<CourseScoreRespDTO> queryCourseScores(CourseScoreQueryReqDTO queryReq);
/**
*
*/
BasePageDTO<CourseScoreRespDTO> queryStudentScores(String studentId, Integer pageNum, Integer pageSize);
/**
*
*/
BasePageDTO<CourseScoreRespDTO> queryCourseStudentScores(String courseId, Integer pageNum, Integer pageSize);
}

@ -1,52 +0,0 @@
package yuxingshi.sms.server.service;
import yuxingshi.sms.server.domain.entity.Course;
import yuxingshi.sms.server.domain.dto.course.CourseManageDTO;
import yuxingshi.sms.server.domain.dto.course.CourseQueryReqDTO;
import yuxingshi.sms.server.domain.dto.course.CourseRespDTO;
import yuxingshi.sms.common.core.domain.dto.BasePageDTO;
/**
*
*/
public interface CourseService {
/**
*
*/
String createCourse(CourseManageDTO dto);
/**
*
*/
void updateCourse(String courseId, CourseManageDTO dto);
/**
*
*/
void deleteCourse(String courseId);
/**
* ID
*/
Course getCourseById(String courseId);
/**
*
*/
Course getCourseByCode(String courseCode);
/**
*
*/
void disableCourse(String courseId);
/**
*
*/
void enableCourse(String courseId);
/**
*
*/
BasePageDTO<CourseRespDTO> queryCourses(CourseQueryReqDTO queryReq);
}

@ -1,6 +1,7 @@
package yuxingshi.sms.server.service;
import org.springframework.web.multipart.MultipartFile;
import yuxingshi.sms.server.domain.dto.stu.StudentDetailDTO;
import yuxingshi.sms.server.domain.entity.Student;
import yuxingshi.sms.server.domain.dto.stu.StudentManageDTO;
import yuxingshi.sms.server.domain.dto.stu.StudentQueryReqDTO;
@ -61,4 +62,10 @@ public interface StudentService {
* Excel
*/
ImportResultDTO importStudents(MultipartFile file);
/**
*
*/
StudentDetailDTO getStudentDetail(String studentId);
}

@ -58,4 +58,11 @@ public interface TeacherService {
* @return
*/
ImportResultDTO importTeachers(MultipartFile file);
/**
*
*/
TeacherRespDTO getTeacherDetail(String teacherId);
}

@ -13,15 +13,12 @@ import org.springframework.web.multipart.MultipartFile;
import yuxingshi.sms.common.core.domain.dto.BasePageDTO;
import yuxingshi.sms.common.core.domain.dto.excel.ImportErrorDTO;
import yuxingshi.sms.common.core.domain.dto.excel.ImportResultDTO;
import yuxingshi.sms.common.core.domain.enums.EntityStatus;
import yuxingshi.sms.common.core.domain.enums.UserStatus;
import yuxingshi.sms.common.core.exception.ServiceException;
import yuxingshi.sms.common.core.utils.ExcelImportUtil;
import yuxingshi.sms.common.core.utils.StringUtil;
import yuxingshi.sms.server.domain.dto.stu_class.ClassExcelDTO;
import yuxingshi.sms.server.domain.dto.stu_class.ClassManageDTO;
import yuxingshi.sms.server.domain.dto.stu_class.ClassQueryReqDTO;
import yuxingshi.sms.server.domain.dto.stu_class.ClassReadListener;
import yuxingshi.sms.server.domain.dto.stu_class.ClassRespDTO;
import yuxingshi.sms.server.domain.dto.stu_class.*;
import yuxingshi.sms.server.domain.entity.Classes;
import yuxingshi.sms.server.domain.entity.Major;
import yuxingshi.sms.server.domain.entity.Student;
@ -71,7 +68,7 @@ public class ClassesServiceImpl extends ServiceImpl<ClassesMapper, Classes> impl
classes.setClassCode(dto.getClassCode());
classes.setMajorId(dto.getMajorId());
classes.setDescription(dto.getDescription());
classes.setStatus(1);
classes.setStatus(EntityStatus.ACTIVE.getValue());
classes.setCreatedTime(LocalDateTime.now());
classes.setUpdatedTime(LocalDateTime.now());
@ -147,7 +144,7 @@ public class ClassesServiceImpl extends ServiceImpl<ClassesMapper, Classes> impl
// 更新班级状态
LambdaUpdateWrapper<Classes> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Classes::getId, classId)
.set(Classes::getStatus, 0)
.set(Classes::getStatus, EntityStatus.DISABLED.getValue())
.set(Classes::getUpdatedTime, LocalDateTime.now());
classesMapper.update(null, updateWrapper);
@ -160,7 +157,7 @@ public class ClassesServiceImpl extends ServiceImpl<ClassesMapper, Classes> impl
// 禁用学生
LambdaUpdateWrapper<Student> studentUpdateWrapper = new LambdaUpdateWrapper<>();
studentUpdateWrapper.eq(Student::getId, student.getId())
.set(Student::getStatus, 0)
.set(Student::getStatus, EntityStatus.DISABLED.getValue())
.set(Student::getUpdatedTime, LocalDateTime.now());
studentMapper.update(null, studentUpdateWrapper);
@ -186,7 +183,7 @@ public class ClassesServiceImpl extends ServiceImpl<ClassesMapper, Classes> impl
LambdaUpdateWrapper<Classes> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Classes::getId, classId)
.set(Classes::getStatus, 1)
.set(Classes::getStatus, EntityStatus.ACTIVE.getValue())
.set(Classes::getUpdatedTime, LocalDateTime.now());
classesMapper.update(null, updateWrapper);
log.info("启用班级成功: classId={}", classId);
@ -234,137 +231,167 @@ public class ClassesServiceImpl extends ServiceImpl<ClassesMapper, Classes> impl
/**
*
* @param file Excel
* @return
* Excel
* majorId
*/
@Transactional
@Override
public ImportResultDTO importClasses(MultipartFile file) {
// 1. 文件校验
// 1. 文件格式和大小校验
ImportResultDTO validateResult = ExcelImportUtil.validateFile(file, maxFileSize);
if (validateResult != null) {
return validateResult;
}
if (validateResult != null) return validateResult;
try {
// 2. 读取Excel
ClassReadListener listener = new ClassReadListener();
ExcelImportUtil.readExcel(file, ClassExcelDTO.class, listener);
// 2. 读取Excel数据
List<ExcelImportUtil.RowData<ClassExcelDTO>> rows = new ArrayList<>();
List<ImportErrorDTO> errors = new ArrayList<>();
readClassExcel(file, rows, errors);
List<ExcelImportUtil.RowData<ClassExcelDTO>> rows = listener.getRows();
List<ImportErrorDTO> errors = new ArrayList<>(listener.getErrors());
if (rows.isEmpty()) return ImportResultDTO.emptyData(errors);
if (rows.isEmpty()) {
return ImportResultDTO.emptyData(errors);
// 3. 校验必填字段和Excel内数据重复性
List<ExcelImportUtil.RowData<ClassExcelDTO>> validRows = validateClassData(rows, errors);
if (validRows.isEmpty()) {
return ImportResultDTO.builder().success(false).totalCount(rows.size()).successCount(0)
.failureCount(errors.size()).errors(errors).message("没有可导入的有效数据").build();
}
// 3. 数据校验:必填字段 + Excel内去重
Set<String> codesInExcel = new HashSet<>();
List<ExcelImportUtil.RowData<ClassExcelDTO>> validRows = new ArrayList<>();
for (ExcelImportUtil.RowData<ClassExcelDTO> row : rows) {
int rowNo = row.getRowNo();
ClassExcelDTO data = row.getData();
String classCode = data.getClassCode();
String majorId = data.getMajorId();
// 校验必填字段
if (StringUtil.isBlank(classCode)) {
errors.add(new ImportErrorDTO(rowNo, "班级代码不能为空", null));
continue;
}
if (StringUtil.isBlank(majorId)) {
errors.add(new ImportErrorDTO(rowNo, "专业ID不能为空", null));
continue;
}
// Excel内去重
if (!codesInExcel.add(classCode)) {
errors.add(new ImportErrorDTO(rowNo, "Excel内班级代码重复: " + classCode, null));
continue;
}
validRows.add(row);
// 4. 检查数据库冲突(班级代码、专业代码存在性)
List<Classes> toInsert = checkClassConflictsAndBuild(validRows, errors);
// 5. 批量插入班级
int successCount = batchInsertClasses(toInsert);
log.info("班级批量导入完成: 总数={}, 成功={}, 失败={}", rows.size(), successCount, errors.size());
return ImportResultDTO.success(rows.size(), successCount, errors);
} catch (IOException e) {
log.error("读取Excel文件失败", e);
return ImportResultDTO.fileError("读取文件失败: " + e.getMessage());
}
}
/**
* Excel使ClassReadListener
*/
private void readClassExcel(MultipartFile file, List<ExcelImportUtil.RowData<ClassExcelDTO>> rows,
List<ImportErrorDTO> errors) throws IOException {
ClassReadListener listener = new ClassReadListener();
ExcelImportUtil.readExcel(file, ClassExcelDTO.class, listener);
rows.addAll(listener.getRows());
errors.addAll(listener.getErrors());
}
/**
* Excel
*
*/
private List<ExcelImportUtil.RowData<ClassExcelDTO>> validateClassData(
List<ExcelImportUtil.RowData<ClassExcelDTO>> rows, List<ImportErrorDTO> errors) {
Set<String> codesInExcel = new HashSet<>();
List<ExcelImportUtil.RowData<ClassExcelDTO>> validRows = new ArrayList<>();
for (ExcelImportUtil.RowData<ClassExcelDTO> row : rows) {
ClassExcelDTO data = row.getData();
int rowNo = row.getRowNo();
// 检查必填字段
if (StringUtil.isBlank(data.getClassCode())) {
errors.add(new ImportErrorDTO(rowNo, "班级代码不能为空", null));
continue;
}
if (StringUtil.isBlank(data.getMajorCode())) {
errors.add(new ImportErrorDTO(rowNo, "专业代码不能为空", null));
continue;
}
// 检查Excel内班级代码重复
if (!codesInExcel.add(data.getClassCode())) {
errors.add(new ImportErrorDTO(rowNo, "Excel内班级代码重复: " + data.getClassCode(), null));
continue;
}
validRows.add(row);
}
return validRows;
}
if (validRows.isEmpty()) {
return ImportResultDTO.builder()
.success(false)
.totalCount(rows.size())
.successCount(0)
.failureCount(errors.size())
.errors(errors)
.message("没有可导入的有效数据")
.build();
/**
*
* ClassesMajor
* majorId
*/
private List<Classes> checkClassConflictsAndBuild(
List<ExcelImportUtil.RowData<ClassExcelDTO>> validRows, List<ImportErrorDTO> errors) {
List<Classes> toInsert = new ArrayList<>();
// 收集需要检查的班级代码和专业代码
List<String> classCodes = validRows.stream()
.map(r -> r.getData().getClassCode()).distinct().toList();
List<String> majorCodes = validRows.stream()
.map(r -> r.getData().getMajorCode()).distinct().toList();
// 批量查询数据库进行冲突检查
Set<String> dbExistCodes = new HashSet<>(
classesMapper.selectList(new LambdaQueryWrapper<Classes>().in(Classes::getClassCode, classCodes))
.stream().map(Classes::getClassCode).toList());
Map<String, Major> majorMap = majorMapper.selectList(
new LambdaQueryWrapper<Major>().in(Major::getMajorCode, majorCodes))
.stream().collect(Collectors.toMap(Major::getMajorCode, m -> m));
// 逐条检查冲突并构建待插入数据
for (ExcelImportUtil.RowData<ClassExcelDTO> row : validRows) {
ClassExcelDTO data = row.getData();
int rowNo = row.getRowNo();
// 检查班级代码冲突
if (dbExistCodes.contains(data.getClassCode())) {
errors.add(new ImportErrorDTO(rowNo, "班级代码已存在: " + data.getClassCode(), null));
continue;
}
// 4. 数据库去重校验
List<String> codes = validRows.stream()
.map(r -> r.getData().getClassCode())
.distinct()
.toList();
Set<String> dbExistSet = new HashSet<>(
classesMapper.selectList(new LambdaQueryWrapper<Classes>().in(Classes::getClassCode, codes))
.stream()
.map(Classes::getClassCode)
.toList()
);
// 5. 校验专业ID是否存在
List<String> majorIds = validRows.stream()
.map(r -> r.getData().getMajorId())
.distinct()
.toList();
Set<String> existMajorIds = new HashSet<>(
majorMapper.selectList(new LambdaQueryWrapper<Major>().in(Major::getId, majorIds))
.stream()
.map(Major::getId)
.toList()
);
// 6. 构建待插入数据
List<Classes> toInsert = new ArrayList<>();
for (ExcelImportUtil.RowData<ClassExcelDTO> row : validRows) {
int rowNo = row.getRowNo();
ClassExcelDTO data = row.getData();
if (dbExistSet.contains(data.getClassCode())) {
errors.add(new ImportErrorDTO(rowNo, "班级代码已存在: " + data.getClassCode(), null));
continue;
}
if (!existMajorIds.contains(data.getMajorId())) {
errors.add(new ImportErrorDTO(rowNo, "专业ID不存在: " + data.getMajorId(), null));
continue;
}
Classes classes = new Classes();
classes.setClassCode(data.getClassCode());
classes.setMajorId(data.getMajorId());
classes.setDescription(data.getDescription());
classes.setStatus(1);
classes.setCreatedTime(LocalDateTime.now());
classes.setUpdatedTime(LocalDateTime.now());
toInsert.add(classes);
// 检查专业代码存在性
Major major = majorMap.get(data.getMajorCode());
if (major == null) {
errors.add(new ImportErrorDTO(rowNo, "专业代码不存在: " + data.getMajorCode(), null));
continue;
}
// 7. 批量插入
int successCount = 0;
if (!toInsert.isEmpty()) {
List<List<Classes>> partitions = ExcelImportUtil.partition(toInsert, batchSize);
for (List<Classes> batch : partitions) {
if (saveBatch(batch)) {
successCount += batch.size();
}
}
// 构建待插入的班级对象使用专业ID而不是专业代码
Classes classes = new Classes();
classes.setClassCode(data.getClassCode());
classes.setMajorId(major.getId()); // 使用查询到的专业ID
classes.setDescription(data.getDescription());
classes.setStatus(EntityStatus.ACTIVE.getValue());
classes.setCreatedTime(LocalDateTime.now());
classes.setUpdatedTime(LocalDateTime.now());
toInsert.add(classes);
}
return toInsert;
}
/**
*
* 使MyBatisPlussaveBatch
*/
private int batchInsertClasses(List<Classes> toInsert) {
int successCount = 0;
if (!toInsert.isEmpty()) {
List<List<Classes>> partitions = ExcelImportUtil.partition(toInsert, batchSize);
for (List<Classes> batch : partitions) {
if (saveBatch(batch)) successCount += batch.size();
}
}
return successCount;
}
log.info("班级批量导入完成: 总数={}, 成功={}, 失败={}", rows.size(), successCount, errors.size());
return ImportResultDTO.success(rows.size(), successCount, errors);
} catch (IOException e) {
log.error("读取Excel文件失败", e);
return ImportResultDTO.fileError("读取文件失败: " + e.getMessage());
/**
*
*/
@Override
public ClassDetailDTO getClassDetail(String classId) {
ClassDetailDTO classDetailDTO = classesMapper.getClassWithMajorById(classId);
if(classDetailDTO == null) {
throw new ServiceException("班级不存在");
}
return classDetailDTO;
}
}

@ -1,332 +0,0 @@
package yuxingshi.sms.server.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import yuxingshi.sms.server.domain.entity.Course;
import yuxingshi.sms.server.domain.entity.CourseScore;
import yuxingshi.sms.server.domain.entity.Student;
import yuxingshi.sms.server.domain.dto.score.CourseScoreManageDTO;
import yuxingshi.sms.server.domain.dto.score.CourseScoreQueryReqDTO;
import yuxingshi.sms.server.domain.dto.score.CourseScoreRespDTO;
import yuxingshi.sms.server.mapper.CourseScoreMapper;
import yuxingshi.sms.server.mapper.CourseMapper;
import yuxingshi.sms.server.mapper.StudentMapper;
import yuxingshi.sms.server.service.CourseScoreService;
import yuxingshi.sms.server.utils.CourseScoreUtil;
import yuxingshi.sms.common.core.domain.dto.BasePageDTO;
import yuxingshi.sms.common.core.exception.ServiceException;
import yuxingshi.sms.common.core.utils.StringUtil;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
public class CourseScoreServiceImpl implements CourseScoreService {
@Autowired
private CourseScoreMapper courseScoreMapper;
@Autowired
private CourseMapper courseMapper;
@Autowired
private StudentMapper studentMapper;
@Override
public String addCourseScore(CourseScoreManageDTO dto) {
// 检查课程是否存在
Course course = courseMapper.selectById(dto.getCourseId());
if (course == null) {
throw new ServiceException("课程不存在");
}
// 检查学生是否存在
Student student = studentMapper.selectById(dto.getStudentId());
if (student == null) {
throw new ServiceException("学生不存在");
}
// 检查该学生是否已有该课程的成绩
CourseScore existing = getCourseScore(dto.getCourseId(), dto.getStudentId());
if (existing != null) {
throw new ServiceException("该学生已有该课程的成绩,请使用更新接口修改");
}
// 计算总成绩和等级
BigDecimal totalScore = CourseScoreUtil.calculateTotalScore(
dto.getUsualScore(), dto.getMidtermScore(), dto.getFinalScore());
String grade = CourseScoreUtil.calculateGrade(totalScore);
CourseScore courseScore = new CourseScore();
courseScore.setCourseId(dto.getCourseId());
courseScore.setStudentId(dto.getStudentId());
courseScore.setUsualScore(dto.getUsualScore());
courseScore.setMidtermScore(dto.getMidtermScore());
courseScore.setFinalScore(dto.getFinalScore());
courseScore.setTotalScore(totalScore);
courseScore.setGrade(grade);
courseScore.setRemark(dto.getRemark());
courseScore.setStatus(1);
courseScore.setCreatedTime(LocalDateTime.now());
courseScore.setUpdatedTime(LocalDateTime.now());
courseScoreMapper.insert(courseScore);
log.info("添加课程成绩成功: courseId={}, studentId={}, totalScore={}, grade={}",
dto.getCourseId(), dto.getStudentId(), totalScore, grade);
return courseScore.getId();
}
@Override
public void updateCourseScore(String scoreId, CourseScoreManageDTO dto) {
CourseScore courseScore = getCourseScoreById(scoreId);
if (courseScore == null) {
throw new ServiceException("成绩不存在");
}
// 检查课程是否存在
Course course = courseMapper.selectById(dto.getCourseId());
if (course == null) {
throw new ServiceException("课程不存在");
}
// 检查学生是否存在
Student student = studentMapper.selectById(dto.getStudentId());
if (student == null) {
throw new ServiceException("学生不存在");
}
// 如果更改了课程或学生,检查新的课程+学生组合是否已存在
if (!dto.getCourseId().equals(courseScore.getCourseId()) ||
!dto.getStudentId().equals(courseScore.getStudentId())) {
CourseScore existing = getCourseScore(dto.getCourseId(), dto.getStudentId());
if (existing != null) {
throw new ServiceException("该学生已有该课程的成绩");
}
}
// 重新计算总成绩和等级
BigDecimal totalScore = CourseScoreUtil.calculateTotalScore(
dto.getUsualScore(), dto.getMidtermScore(), dto.getFinalScore());
String grade = CourseScoreUtil.calculateGrade(totalScore);
courseScore.setCourseId(dto.getCourseId());
courseScore.setStudentId(dto.getStudentId());
courseScore.setUsualScore(dto.getUsualScore());
courseScore.setMidtermScore(dto.getMidtermScore());
courseScore.setFinalScore(dto.getFinalScore());
courseScore.setTotalScore(totalScore);
courseScore.setGrade(grade);
courseScore.setRemark(dto.getRemark());
courseScore.setUpdatedTime(LocalDateTime.now());
courseScoreMapper.updateById(courseScore);
log.info("更新课程成绩成功: scoreId={}, totalScore={}, grade={}", scoreId, totalScore, grade);
}
@Override
public void deleteCourseScore(String scoreId) {
CourseScore courseScore = getCourseScoreById(scoreId);
if (courseScore == null) {
throw new ServiceException("成绩不存在");
}
courseScoreMapper.deleteById(scoreId);
log.info("删除课程成绩成功: scoreId={}", scoreId);
}
@Override
public CourseScore getCourseScoreById(String scoreId) {
return courseScoreMapper.selectById(scoreId);
}
@Override
public CourseScore getCourseScore(String courseId, String studentId) {
LambdaQueryWrapper<CourseScore> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CourseScore::getCourseId, courseId)
.eq(CourseScore::getStudentId, studentId)
.eq(CourseScore::getStatus, 1);
return courseScoreMapper.selectOne(queryWrapper);
}
@Override
public void deleteCourseAllScores(String courseId) {
LambdaQueryWrapper<CourseScore> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CourseScore::getCourseId, courseId);
courseScoreMapper.delete(queryWrapper);
log.info("删除课程所有成绩成功: courseId={}", courseId);
}
@Override
public void deleteStudentAllScores(String studentId) {
LambdaQueryWrapper<CourseScore> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CourseScore::getStudentId, studentId);
courseScoreMapper.delete(queryWrapper);
log.info("删除学生所有成绩成功: studentId={}", studentId);
}
@Override
public BasePageDTO<CourseScoreRespDTO> queryCourseScores(CourseScoreQueryReqDTO queryReq) {
LambdaQueryWrapper<CourseScore> queryWrapper = new LambdaQueryWrapper<>();
// 根据课程ID查询
if (queryReq.getCourseId() != null) {
queryWrapper.eq(CourseScore::getCourseId, queryReq.getCourseId());
}
// 根据学生ID查询
if (queryReq.getStudentId() != null) {
queryWrapper.eq(CourseScore::getStudentId, queryReq.getStudentId());
}
// 根据等级查询
if (StringUtil.isNotEmpty(queryReq.getGrade())) {
queryWrapper.eq(CourseScore::getGrade, queryReq.getGrade());
}
// 状态过滤
queryWrapper.eq(CourseScore::getStatus, 1);
// 排序
if (StringUtil.isNotEmpty(queryReq.getSortBy())) {
if ("total_score".equals(queryReq.getSortBy())) {
queryWrapper.orderByDesc(CourseScore::getTotalScore);
} else if ("grade".equals(queryReq.getSortBy())) {
queryWrapper.orderByAsc(CourseScore::getGrade);
} else {
queryWrapper.orderByDesc(CourseScore::getCreatedTime);
}
} else {
queryWrapper.orderByDesc(CourseScore::getCreatedTime);
}
// 使用MyBatisPlus分页插件
Page<CourseScore> page = new Page<>(queryReq.getPageNo(), queryReq.getPageSize());
Page<CourseScore> pageResult = courseScoreMapper.selectPage(page, queryWrapper);
List<CourseScoreRespDTO> respList = pageResult.getRecords().stream()
.map(score -> {
// 查询课程信息
Course course = courseMapper.selectById(score.getCourseId());
// 查询学生信息
Student student = studentMapper.selectById(score.getStudentId());
return CourseScoreRespDTO.builder()
.id(score.getId())
.courseId(score.getCourseId())
.courseName(course != null ? course.getCourseName() : "")
.courseCode(course != null ? course.getCourseCode() : "")
.studentId(score.getStudentId())
.studentNo(student != null ? student.getStudentNo() : "")
.studentName(student != null ? student.getRealName() : "")
.usualScore(score.getUsualScore())
.midtermScore(score.getMidtermScore())
.finalScore(score.getFinalScore())
.totalScore(score.getTotalScore())
.grade(score.getGrade())
.remark(score.getRemark())
.createdTime(score.getCreatedTime())
.updatedTime(score.getUpdatedTime())
.build();
})
.collect(Collectors.toList());
return BasePageDTO.<CourseScoreRespDTO>builder()
.list(respList)
.totals(pageResult.getTotal())
.totalPages(BasePageDTO.calculateTotalPages(pageResult.getTotal(), queryReq.getPageSize()))
.build();
}
@Override
public BasePageDTO<CourseScoreRespDTO> queryStudentScores(String studentId, Integer pageNum, Integer pageSize) {
LambdaQueryWrapper<CourseScore> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CourseScore::getStudentId, studentId)
.eq(CourseScore::getStatus, 1)
.orderByDesc(CourseScore::getCreatedTime);
// 使用MyBatisPlus分页插件
Page<CourseScore> page = new Page<>(pageNum, pageSize);
Page<CourseScore> pageResult = courseScoreMapper.selectPage(page, queryWrapper);
List<CourseScoreRespDTO> respList = pageResult.getRecords().stream()
.map(score -> {
// 查询课程信息
Course course = courseMapper.selectById(score.getCourseId());
// 查询学生信息
Student student = studentMapper.selectById(score.getStudentId());
return CourseScoreRespDTO.builder()
.id(score.getId())
.courseId(score.getCourseId())
.courseName(course != null ? course.getCourseName() : "")
.courseCode(course != null ? course.getCourseCode() : "")
.studentId(score.getStudentId())
.studentNo(student != null ? student.getStudentNo() : "")
.studentName(student != null ? student.getRealName() : "")
.usualScore(score.getUsualScore())
.midtermScore(score.getMidtermScore())
.finalScore(score.getFinalScore())
.totalScore(score.getTotalScore())
.grade(score.getGrade())
.remark(score.getRemark())
.createdTime(score.getCreatedTime())
.updatedTime(score.getUpdatedTime())
.build();
})
.collect(Collectors.toList());
return BasePageDTO.<CourseScoreRespDTO>builder()
.list(respList)
.totals(pageResult.getTotal())
.totalPages(BasePageDTO.calculateTotalPages(pageResult.getTotal(), pageSize))
.build();
}
@Override
public BasePageDTO<CourseScoreRespDTO> queryCourseStudentScores(String courseId, Integer pageNum, Integer pageSize) {
LambdaQueryWrapper<CourseScore> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CourseScore::getCourseId, courseId)
.eq(CourseScore::getStatus, 1)
.orderByDesc(CourseScore::getTotalScore);
// 使用MyBatisPlus分页插件
Page<CourseScore> page = new Page<>(pageNum, pageSize);
Page<CourseScore> pageResult = courseScoreMapper.selectPage(page, queryWrapper);
List<CourseScoreRespDTO> respList = pageResult.getRecords().stream()
.map(score -> {
// 查询课程信息
Course course = courseMapper.selectById(score.getCourseId());
// 查询学生信息
Student student = studentMapper.selectById(score.getStudentId());
return CourseScoreRespDTO.builder()
.id(score.getId())
.courseId(score.getCourseId())
.courseName(course != null ? course.getCourseName() : "")
.courseCode(course != null ? course.getCourseCode() : "")
.studentId(score.getStudentId())
.studentNo(student != null ? student.getStudentNo() : "")
.studentName(student != null ? student.getRealName() : "")
.usualScore(score.getUsualScore())
.midtermScore(score.getMidtermScore())
.finalScore(score.getFinalScore())
.totalScore(score.getTotalScore())
.grade(score.getGrade())
.remark(score.getRemark())
.createdTime(score.getCreatedTime())
.updatedTime(score.getUpdatedTime())
.build();
})
.collect(Collectors.toList());
return BasePageDTO.<CourseScoreRespDTO>builder()
.list(respList)
.totals(pageResult.getTotal())
.totalPages(BasePageDTO.calculateTotalPages(pageResult.getTotal(), pageSize))
.build();
}
}

@ -1,271 +0,0 @@
package yuxingshi.sms.server.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import yuxingshi.sms.server.domain.entity.Course;
import yuxingshi.sms.server.domain.entity.CourseScore;
import yuxingshi.sms.server.domain.entity.Teacher;
import yuxingshi.sms.server.domain.dto.course.CourseManageDTO;
import yuxingshi.sms.server.domain.dto.course.CourseQueryReqDTO;
import yuxingshi.sms.server.domain.dto.course.CourseRespDTO;
import yuxingshi.sms.server.mapper.CourseMapper;
import yuxingshi.sms.server.mapper.CourseScoreMapper;
import yuxingshi.sms.server.mapper.TeacherMapper;
import yuxingshi.sms.server.service.CourseService;
import yuxingshi.sms.common.core.domain.dto.BasePageDTO;
import yuxingshi.sms.common.core.domain.enums.UserRole;
import yuxingshi.sms.common.core.exception.ServiceException;
import yuxingshi.sms.common.core.utils.StringUtil;
import yuxingshi.sms.common.core.utils.ThreadLocalUtil;
import yuxingshi.sms.common.core.constants.SecurityConstants;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
public class CourseServiceImpl implements CourseService {
@Autowired
private CourseMapper courseMapper;
@Autowired
private TeacherMapper teacherMapper;
@Autowired
private CourseScoreMapper courseScoreMapper;
@Override
public String createCourse(CourseManageDTO dto) {
// 检查教师是否存在
Teacher teacher = teacherMapper.selectById(dto.getTeacherId());
if (teacher == null) {
throw new ServiceException("教师不存在");
}
// 权限检查:教师只能为自己创建课程
String currentUserId = ThreadLocalUtil.get(SecurityConstants.USER_ID, String.class);
Integer userIdentity = ThreadLocalUtil.get(SecurityConstants.USER_IDENTITY, Integer.class);
// 如果是教师角色,只能为自己创建课程
if (UserRole.ROLE_TEACHER.getValue().equals(userIdentity)) {
if (teacher.getUserId() == null || !currentUserId.equals(teacher.getUserId())) {
throw new ServiceException("无权限为其他教师创建课程");
}
}
// 检查课程代码是否已存在
Course codeExist = getCourseByCode(dto.getCourseCode());
if (codeExist != null) {
throw new ServiceException("课程代码已存在");
}
Course course = new Course();
course.setTeacherId(dto.getTeacherId());
course.setCourseName(dto.getCourseName());
course.setCourseCode(dto.getCourseCode());
course.setCredit(dto.getCredit());
course.setTotalHours(dto.getTotalHours());
course.setDescription(dto.getDescription());
course.setStatus(1);
course.setCreatedTime(LocalDateTime.now());
course.setUpdatedTime(LocalDateTime.now());
courseMapper.insert(course);
log.info("创建课程成功: courseCode={}, courseName={}, teacherId={}", dto.getCourseCode(), dto.getCourseName(), dto.getTeacherId());
return course.getId();
}
@Override
public void updateCourse(String courseId, CourseManageDTO dto) {
Course course = getCourseById(courseId);
if (course == null) {
throw new ServiceException("课程不存在");
}
// 权限检查:教师只能操作自己创建的课程
checkCoursePermission(courseId);
// 检查教师是否存在
Teacher teacher = teacherMapper.selectById(dto.getTeacherId());
if (teacher == null) {
throw new ServiceException("教师不存在");
}
// 检查课程代码是否被其他课程使用
if (!dto.getCourseCode().equals(course.getCourseCode())) {
Course codeExist = getCourseByCode(dto.getCourseCode());
if (codeExist != null) {
throw new ServiceException("课程代码已存在");
}
}
course.setTeacherId(dto.getTeacherId());
course.setCourseName(dto.getCourseName());
course.setCourseCode(dto.getCourseCode());
course.setCredit(dto.getCredit());
course.setTotalHours(dto.getTotalHours());
course.setDescription(dto.getDescription());
course.setUpdatedTime(LocalDateTime.now());
courseMapper.updateById(course);
log.info("更新课程成功: courseId={}", courseId);
}
@Override
public void deleteCourse(String courseId) {
Course course = getCourseById(courseId);
if (course == null) {
throw new ServiceException("课程不存在");
}
// 权限检查:教师只能操作自己创建的课程
checkCoursePermission(courseId);
// 检查是否有关联的成绩记录
LambdaQueryWrapper<CourseScore> scoreQuery = new LambdaQueryWrapper<>();
scoreQuery.eq(CourseScore::getCourseId, courseId);
long scoreCount = courseScoreMapper.selectCount(scoreQuery);
if (scoreCount > 0) {
throw new ServiceException("课程存在成绩记录,无法删除。请先删除相关成绩记录");
}
courseMapper.deleteById(courseId);
log.info("删除课程成功: courseId={}", courseId);
}
@Override
public Course getCourseById(String courseId) {
return courseMapper.selectById(courseId);
}
@Override
public Course getCourseByCode(String courseCode) {
LambdaQueryWrapper<Course> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Course::getCourseCode, courseCode);
return courseMapper.selectOne(queryWrapper);
}
@Override
public void disableCourse(String courseId) {
Course course = getCourseById(courseId);
if (course == null) {
throw new ServiceException("课程不存在");
}
// 权限检查:教师只能操作自己创建的课程
checkCoursePermission(courseId);
LambdaUpdateWrapper<Course> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Course::getId, courseId)
.set(Course::getStatus, 0)
.set(Course::getUpdatedTime, LocalDateTime.now());
courseMapper.update(null, updateWrapper);
log.info("禁用课程成功: courseId={}", courseId);
}
@Override
public void enableCourse(String courseId) {
Course course = getCourseById(courseId);
if (course == null) {
throw new ServiceException("课程不存在");
}
// 权限检查:教师只能操作自己创建的课程
checkCoursePermission(courseId);
LambdaUpdateWrapper<Course> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Course::getId, courseId)
.set(Course::getStatus, 1)
.set(Course::getUpdatedTime, LocalDateTime.now());
courseMapper.update(null, updateWrapper);
log.info("启用课程成功: courseId={}", courseId);
}
@Override
public BasePageDTO<CourseRespDTO> queryCourses(CourseQueryReqDTO queryReq) {
LambdaQueryWrapper<Course> queryWrapper = new LambdaQueryWrapper<>();
// 根据教师ID查询
if (queryReq.getTeacherId() != null) {
queryWrapper.eq(Course::getTeacherId, queryReq.getTeacherId());
}
// 根据课程名称模糊查询
if (StringUtil.isNotEmpty(queryReq.getCourseName())) {
queryWrapper.like(Course::getCourseName, queryReq.getCourseName());
}
// 根据课程代码模糊查询
if (StringUtil.isNotEmpty(queryReq.getCourseCode())) {
queryWrapper.like(Course::getCourseCode, queryReq.getCourseCode());
}
// 根据状态查询
if (queryReq.getStatus() != null) {
queryWrapper.eq(Course::getStatus, queryReq.getStatus());
}
// 按创建时间降序排序
queryWrapper.orderByDesc(Course::getCreatedTime);
// 使用MyBatisPlus分页插件
Page<Course> page = new Page<>(queryReq.getPageNo(), queryReq.getPageSize());
Page<Course> pageResult = courseMapper.selectPage(page, queryWrapper);
List<CourseRespDTO> respList = pageResult.getRecords().stream()
.map(course -> CourseRespDTO.builder()
.id(course.getId())
.teacherId(course.getTeacherId())
.courseName(course.getCourseName())
.courseCode(course.getCourseCode())
.credit(course.getCredit())
.totalHours(course.getTotalHours())
.description(course.getDescription())
.status(course.getStatus())
.createdTime(course.getCreatedTime())
.updatedTime(course.getUpdatedTime())
.build())
.collect(Collectors.toList());
return BasePageDTO.<CourseRespDTO>builder()
.list(respList)
.totals(pageResult.getTotal())
.totalPages(BasePageDTO.calculateTotalPages(pageResult.getTotal(), queryReq.getPageSize()))
.build();
}
/**
*
*
*/
private void checkCoursePermission(String courseId) {
String currentUserId = ThreadLocalUtil.get(SecurityConstants.USER_ID, String.class);
Integer userIdentity = ThreadLocalUtil.get(SecurityConstants.USER_IDENTITY, Integer.class);
// 管理员和系统管理员可以操作所有课程
if (UserRole.ROLE_ADMIN.getValue().equals(userIdentity) ||
UserRole.ROLE_SYS_ADMIN.getValue().equals(userIdentity)) {
return;
}
// 教师只能操作自己创建的课程
if (UserRole.ROLE_TEACHER.getValue().equals(userIdentity)) {
Course course = getCourseById(courseId);
if (course == null) {
throw new ServiceException("课程不存在");
}
// 通过teacherId查找对应的userId
Teacher teacher = teacherMapper.selectById(course.getTeacherId());
if (teacher == null || !currentUserId.equals(teacher.getUserId())) {
throw new ServiceException("无权限操作该课程");
}
}
}
}

@ -13,6 +13,7 @@ import org.springframework.web.multipart.MultipartFile;
import yuxingshi.sms.common.core.domain.dto.BasePageDTO;
import yuxingshi.sms.common.core.domain.dto.excel.ImportErrorDTO;
import yuxingshi.sms.common.core.domain.dto.excel.ImportResultDTO;
import yuxingshi.sms.common.core.domain.enums.EntityStatus;
import yuxingshi.sms.common.core.exception.ServiceException;
import yuxingshi.sms.common.core.utils.ExcelImportUtil;
import yuxingshi.sms.common.core.utils.StringUtil;
@ -59,7 +60,7 @@ public class MajorServiceImpl extends ServiceImpl<MajorMapper,Major> implements
major.setMajorCode(dto.getMajorCode());
major.setMajorName(dto.getMajorName());
major.setDescription(dto.getDescription());
major.setStatus(1);
major.setStatus(EntityStatus.ACTIVE.getValue());
major.setCreatedTime(LocalDateTime.now());
major.setUpdatedTime(LocalDateTime.now());
@ -138,7 +139,7 @@ public class MajorServiceImpl extends ServiceImpl<MajorMapper,Major> implements
LambdaUpdateWrapper<Major> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Major::getId, majorId)
.set(Major::getStatus, 0)
.set(Major::getStatus, EntityStatus.DISABLED.getValue())
.set(Major::getUpdatedTime, LocalDateTime.now());
majorMapper.update(null, updateWrapper);
@ -154,7 +155,7 @@ public class MajorServiceImpl extends ServiceImpl<MajorMapper,Major> implements
LambdaUpdateWrapper<Major> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Major::getId, majorId)
.set(Major::getStatus, 1)
.set(Major::getStatus, EntityStatus.ACTIVE.getValue())
.set(Major::getUpdatedTime, LocalDateTime.now());
majorMapper.update(null, updateWrapper);
@ -202,122 +203,134 @@ public class MajorServiceImpl extends ServiceImpl<MajorMapper,Major> implements
/**
*
* @param file
* @return
* Excel
*
*/
@Transactional
@Override
public ImportResultDTO importMajors(MultipartFile file) {
// 1. 文件校验
// 1. 文件格式和大小校验
ImportResultDTO validateResult = ExcelImportUtil.validateFile(file, maxFileSize);
if (validateResult != null) {
return validateResult;
}
if (validateResult != null) return validateResult;
try {
// 2. 读取Excel
MajorReadListener listener = new MajorReadListener();
ExcelImportUtil.readExcel(file, MajorExcelDTO.class, listener);
List<ExcelImportUtil.RowData<MajorExcelDTO>> rows = listener.getRows();
List<ImportErrorDTO> errors = new ArrayList<>(listener.getErrors());
// 2. 读取Excel数据
List<ExcelImportUtil.RowData<MajorExcelDTO>> rows = new ArrayList<>();
List<ImportErrorDTO> errors = new ArrayList<>();
readMajorExcel(file, rows, errors);
if (rows.isEmpty()) {
return ImportResultDTO.emptyData(errors);
}
// 3. 数据校验:必填字段 + Excel内去重
Set<String> codesInExcel = new HashSet<>();
List<ExcelImportUtil.RowData<MajorExcelDTO>> validRows = new ArrayList<>();
for (ExcelImportUtil.RowData<MajorExcelDTO> row : rows) {
int rowNo = row.getRowNo();
MajorExcelDTO data = row.getData();
String code = data.getMajorCode();
String name = data.getMajorName();
// 校验必填字段
if (StringUtil.isBlank(code)) {
errors.add(new ImportErrorDTO(rowNo, "专业代码不能为空", null));
continue;
}
if (StringUtil.isBlank(name)) {
errors.add(new ImportErrorDTO(rowNo, "专业名称不能为空", null));
continue;
}
// Excel内去重
if (!codesInExcel.add(code)) {
errors.add(new ImportErrorDTO(rowNo, "Excel内专业代码重复: " + code, null));
continue;
}
validRows.add(row);
}
if (rows.isEmpty()) return ImportResultDTO.emptyData(errors);
// 3. 校验必填字段和Excel内数据重复性
List<ExcelImportUtil.RowData<MajorExcelDTO>> validRows = validateMajorData(rows, errors);
if (validRows.isEmpty()) {
return ImportResultDTO.builder()
.success(false)
.totalCount(rows.size())
.successCount(0)
.failureCount(errors.size())
.errors(errors)
.message("没有可导入的有效数据")
.build();
return ImportResultDTO.builder().success(false).totalCount(rows.size()).successCount(0)
.failureCount(errors.size()).errors(errors).message("没有可导入的有效数据").build();
}
// 4. 数据库去重校验
List<String> codes = validRows.stream()
.map(r -> r.getData().getMajorCode())
.distinct()
.toList();
Set<String> dbExistSet = new HashSet<>(
majorMapper.selectList(new LambdaQueryWrapper<Major>().in(Major::getMajorCode, codes))
.stream()
.map(Major::getMajorCode)
.toList()
);
// 5. 构建待插入数据
List<Major> toInsert = new ArrayList<>();
for (ExcelImportUtil.RowData<MajorExcelDTO> row : validRows) {
int rowNo = row.getRowNo();
MajorExcelDTO data = row.getData();
if (dbExistSet.contains(data.getMajorCode())) {
errors.add(new ImportErrorDTO(rowNo, "专业代码已存在: " + data.getMajorCode(), null));
continue;
}
Major major = new Major();
major.setMajorCode(data.getMajorCode());
major.setMajorName(data.getMajorName());
major.setDescription(data.getDescription());
major.setStatus(1);
major.setCreatedTime(LocalDateTime.now());
major.setUpdatedTime(LocalDateTime.now());
toInsert.add(major);
}
// 6. 批量插入
int successCount = 0;
if (!toInsert.isEmpty()) {
List<List<Major>> partitions = ExcelImportUtil.partition(toInsert, batchSize);
for (List<Major> batch : partitions) {
if (saveBatch(batch)) {
successCount += batch.size();
}
}
}
// 4. 检查数据库冲突(专业代码重复性)
List<Major> toInsert = checkMajorConflictsAndBuild(validRows, errors);
// 5. 批量插入专业
int successCount = batchInsertMajors(toInsert);
log.info("专业批量导入完成: 总数={}, 成功={}, 失败={}", rows.size(), successCount, errors.size());
return ImportResultDTO.success(rows.size(), successCount, errors);
return ImportResultDTO.success(rows.size(), successCount, errors);
} catch (IOException e) {
log.error("读取Excel文件失败", e);
return ImportResultDTO.fileError("读取文件失败: " + e.getMessage());
}
}
/**
* Excel使MajorReadListener
*/
private void readMajorExcel(MultipartFile file, List<ExcelImportUtil.RowData<MajorExcelDTO>> rows,
List<ImportErrorDTO> errors) throws IOException {
MajorReadListener listener = new MajorReadListener();
ExcelImportUtil.readExcel(file, MajorExcelDTO.class, listener);
rows.addAll(listener.getRows());
errors.addAll(listener.getErrors());
}
/**
* Excel
*
*/
private List<ExcelImportUtil.RowData<MajorExcelDTO>> validateMajorData(
List<ExcelImportUtil.RowData<MajorExcelDTO>> rows, List<ImportErrorDTO> errors) {
Set<String> codesInExcel = new HashSet<>();
List<ExcelImportUtil.RowData<MajorExcelDTO>> validRows = new ArrayList<>();
for (ExcelImportUtil.RowData<MajorExcelDTO> row : rows) {
MajorExcelDTO data = row.getData();
int rowNo = row.getRowNo();
// 检查必填字段
if (StringUtil.isBlank(data.getMajorCode())) {
errors.add(new ImportErrorDTO(rowNo, "专业代码不能为空", null));
continue;
}
if (StringUtil.isBlank(data.getMajorName())) {
errors.add(new ImportErrorDTO(rowNo, "专业名称不能为空", null));
continue;
}
// 检查Excel内专业代码重复
if (!codesInExcel.add(data.getMajorCode())) {
errors.add(new ImportErrorDTO(rowNo, "Excel内专业代码重复: " + data.getMajorCode(), null));
continue;
}
validRows.add(row);
}
return validRows;
}
private List<Major> checkMajorConflictsAndBuild(
List<ExcelImportUtil.RowData<MajorExcelDTO>> validRows, List<ImportErrorDTO> errors) {
List<Major> toInsert = new ArrayList<>();
List<String> codes = validRows.stream()
.map(r -> r.getData().getMajorCode()).distinct().toList();
Set<String> dbExistSet = new HashSet<>(
majorMapper.selectList(new LambdaQueryWrapper<Major>().in(Major::getMajorCode, codes))
.stream().map(Major::getMajorCode).toList());
for (ExcelImportUtil.RowData<MajorExcelDTO> row : validRows) {
MajorExcelDTO data = row.getData();
int rowNo = row.getRowNo();
if (dbExistSet.contains(data.getMajorCode())) {
errors.add(new ImportErrorDTO(rowNo, "专业代码已存在: " + data.getMajorCode(), null));
continue;
}
Major major = new Major();
major.setMajorCode(data.getMajorCode());
major.setMajorName(data.getMajorName());
major.setDescription(data.getDescription());
major.setStatus(EntityStatus.ACTIVE.getValue());
major.setCreatedTime(LocalDateTime.now());
major.setUpdatedTime(LocalDateTime.now());
toInsert.add(major);
}
return toInsert;
}
/**
*
* 使MyBatisPlussaveBatch
*/
private int batchInsertMajors(List<Major> toInsert) {
int successCount = 0;
if (!toInsert.isEmpty()) {
List<List<Major>> partitions = ExcelImportUtil.partition(toInsert, batchSize);
for (List<Major> batch : partitions) {
if (saveBatch(batch)) successCount += batch.size();
}
}
return successCount;
}
/**
* ID

@ -10,24 +10,18 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import yuxingshi.sms.common.core.domain.enums.EntityStatus;
import yuxingshi.sms.common.core.utils.ExcelImportUtil;
import yuxingshi.sms.server.domain.dto.stu.StudentExcelDTO;
import yuxingshi.sms.server.domain.dto.stu.StudentReadListener;
import yuxingshi.sms.server.domain.dto.stu.*;
import yuxingshi.sms.server.domain.entity.Classes;
import yuxingshi.sms.server.domain.entity.Student;
import yuxingshi.sms.server.domain.entity.User;
import yuxingshi.sms.server.domain.dto.stu.StudentManageDTO;
import yuxingshi.sms.server.domain.dto.stu.StudentQueryReqDTO;
import yuxingshi.sms.server.domain.dto.stu.StudentRespDTO;
import yuxingshi.sms.server.mapper.StudentMapper;
import yuxingshi.sms.server.mapper.ClassesMapper;
import yuxingshi.sms.server.mapper.UserMapper;
import yuxingshi.sms.server.mapper.CourseScoreMapper;
import yuxingshi.sms.server.domain.entity.CourseScore;
import yuxingshi.sms.server.service.StudentService;
import yuxingshi.sms.common.core.domain.dto.excel.ImportResultDTO;
import yuxingshi.sms.common.core.domain.dto.excel.ImportErrorDTO;
import yuxingshi.sms.server.utils.PasswordGeneratorUtil;
import yuxingshi.sms.common.core.domain.dto.BasePageDTO;
import yuxingshi.sms.common.core.domain.enums.UserStatus;
import yuxingshi.sms.common.core.exception.ServiceException;
@ -51,9 +45,6 @@ public class StudentServiceImpl implements StudentService {
@Autowired
private UserMapper userMapper;
@Autowired
private CourseScoreMapper courseScoreMapper;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@ -63,6 +54,7 @@ public class StudentServiceImpl implements StudentService {
@Value("${app.import.student.max-file-size-bytes:5242880}")
private long maxFileSize;
@Transactional
@Override
public String createStudent(String userId, StudentManageDTO dto) {
// 检查学号是否已存在
@ -88,13 +80,10 @@ public class StudentServiceImpl implements StudentService {
throw new ServiceException("学号对应的用户名已存在");
}
// 生成随机密码
String randomPassword = PasswordGeneratorUtil.generatePassword();
// 创建用户账号
// 创建用户账号,密码默认为学号
User user = new User();
user.setUsername(dto.getStudentNo());
user.setPassword(passwordEncoder.encode(randomPassword));
user.setPassword(passwordEncoder.encode(dto.getStudentNo()));
user.setEmail(null);
user.setRole(4); // 学生角色
user.setStatus(UserStatus.ACTIVE.getValue());
@ -103,7 +92,7 @@ public class StudentServiceImpl implements StudentService {
userMapper.insert(user);
finalUserId = user.getId();
log.info("为学生创建账号成功: studentNo={}, username={}, password={}", dto.getStudentNo(), dto.getStudentNo(), randomPassword);
log.info("为学生创建账号成功: studentNo={}, username={}", dto.getStudentNo(), dto.getStudentNo());
} else {
// 如果提供了userId检查该用户是否已作为学生存在
Student existStudent = getStudentByUserId(userId);
@ -119,7 +108,7 @@ public class StudentServiceImpl implements StudentService {
student.setRealName(dto.getRealName());
student.setGender(dto.getGender());
student.setPhone(dto.getPhone());
student.setStatus(1);
student.setStatus(EntityStatus.ACTIVE.getValue());
student.setCreatedTime(LocalDateTime.now());
student.setUpdatedTime(LocalDateTime.now());
@ -165,15 +154,6 @@ public class StudentServiceImpl implements StudentService {
throw new ServiceException("学生不存在");
}
// 检查是否有关联的成绩数据
LambdaQueryWrapper<CourseScore> scoreWrapper = new LambdaQueryWrapper<>();
scoreWrapper.eq(CourseScore::getStudentId, studentId)
.eq(CourseScore::getStatus, 1);
long scoreCount = courseScoreMapper.selectCount(scoreWrapper);
if (scoreCount > 0) {
throw new ServiceException("该学生存在关联的成绩数据,无法删除");
}
studentMapper.deleteById(studentId);
log.info("删除学生成功: studentId={}", studentId);
}
@ -208,7 +188,7 @@ public class StudentServiceImpl implements StudentService {
// 更新学生状态
LambdaUpdateWrapper<Student> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Student::getId, studentId)
.set(Student::getStatus, 0)
.set(Student::getStatus, EntityStatus.DISABLED.getValue())
.set(Student::getUpdatedTime, LocalDateTime.now());
studentMapper.update(null, updateWrapper);
@ -235,7 +215,7 @@ public class StudentServiceImpl implements StudentService {
// 更新学生状态
LambdaUpdateWrapper<Student> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Student::getId, studentId)
.set(Student::getStatus, 1)
.set(Student::getStatus, EntityStatus.ACTIVE.getValue())
.set(Student::getUpdatedTime, LocalDateTime.now());
studentMapper.update(null, updateWrapper);
@ -308,170 +288,38 @@ public class StudentServiceImpl implements StudentService {
/**
*
* @param file Excel
* @return
* Excel
* UserStudent使
*/
@Transactional
@Override
public ImportResultDTO importStudents(MultipartFile file) {
// 1. 文件校验
// 1. 文件格式和大小校验
ImportResultDTO validateResult = ExcelImportUtil.validateFile(file, maxFileSize);
if (validateResult != null) {
return validateResult;
}
if (validateResult != null) return validateResult;
try {
// 2. 读取Excel
StudentReadListener listener = new StudentReadListener();
ExcelImportUtil.readExcel(file, StudentExcelDTO.class, listener);
// 2. 读取Excel数据
List<ExcelImportUtil.RowData<StudentExcelDTO>> rows = new ArrayList<>();
List<ImportErrorDTO> errors = new ArrayList<>();
readStudentExcel(file, rows, errors);
List<ExcelImportUtil.RowData<StudentExcelDTO>> rows = listener.getRows();
List<ImportErrorDTO> errors = new ArrayList<>(listener.getErrors());
if (rows.isEmpty()) {
return ImportResultDTO.emptyData(errors);
}
// 3. 数据校验:必填字段 + Excel内去重
Set<String> studentNosInExcel = new HashSet<>();
List<ExcelImportUtil.RowData<StudentExcelDTO>> validRows = new ArrayList<>();
for (ExcelImportUtil.RowData<StudentExcelDTO> row : rows) {
int rowNo = row.getRowNo();
StudentExcelDTO data = row.getData();
String studentNo = data.getStudentNo();
String realName = data.getRealName();
String classCode = data.getClassCode();
// 校验必填字段
if (StringUtil.isBlank(studentNo)) {
errors.add(new ImportErrorDTO(rowNo, "学号不能为空", null));
continue;
}
if (StringUtil.isBlank(realName)) {
errors.add(new ImportErrorDTO(rowNo, "真实姓名不能为空", null));
continue;
}
if (StringUtil.isBlank(classCode)) {
errors.add(new ImportErrorDTO(rowNo, "班级代码不能为空", null));
continue;
}
// Excel内去重
if (!studentNosInExcel.add(studentNo)) {
errors.add(new ImportErrorDTO(rowNo, "Excel内学号重复: " + studentNo, null));
continue;
}
validRows.add(row);
}
if (rows.isEmpty()) return ImportResultDTO.emptyData(errors);
// 3. 校验必填字段和Excel内数据重复性
List<ExcelImportUtil.RowData<StudentExcelDTO>> validRows = validateStudentData(rows, errors);
if (validRows.isEmpty()) {
return ExcelImportUtil.buildNoValidDataResult(rows.size(), errors);
}
// 4. 数据库去重校验(学号)
List<String> studentNos = validRows.stream()
.map(r -> r.getData().getStudentNo())
.distinct()
.toList();
Set<String> dbExistStudentNos = new HashSet<>(
studentMapper.selectList(new LambdaQueryWrapper<Student>().in(Student::getStudentNo, studentNos))
.stream()
.map(Student::getStudentNo)
.toList()
);
// 5. 校验用户名是否已存在
Set<String> dbExistUsernames = new HashSet<>(
userMapper.selectList(new LambdaQueryWrapper<User>().in(User::getUsername, studentNos))
.stream()
.map(User::getUsername)
.toList()
);
// 6. 批量查询班级是否存在
List<String> classCodes = validRows.stream()
.map(r -> r.getData().getClassCode())
.distinct()
.toList();
Map<String, Classes> classMap = classesMapper.selectList(
new LambdaQueryWrapper<Classes>().in(Classes::getClassCode, classCodes)
).stream().collect(Collectors.toMap(Classes::getClassCode, c -> c));
// 7. 构建待插入数据
List<StudentData> toInsert = new ArrayList<>();
for (ExcelImportUtil.RowData<StudentExcelDTO> row : validRows) {
int rowNo = row.getRowNo();
StudentExcelDTO data = row.getData();
if (dbExistStudentNos.contains(data.getStudentNo())) {
errors.add(new ImportErrorDTO(rowNo, "学号已存在: " + data.getStudentNo(), null));
continue;
}
if (dbExistUsernames.contains(data.getStudentNo())) {
errors.add(new ImportErrorDTO(rowNo, "用户名已存在: " + data.getStudentNo(), null));
continue;
}
Classes classes = classMap.get(data.getClassCode());
if (classes == null) {
errors.add(new ImportErrorDTO(rowNo, "班级代码不存在: " + data.getClassCode(), null));
continue;
}
StudentData studentData = new StudentData();
studentData.data = data;
studentData.classId = classes.getId();
studentData.password = PasswordGeneratorUtil.generatePassword();
toInsert.add(studentData);
}
// 8. 批量插入(需要先创建用户,再创建学生)
int successCount = 0;
if (!toInsert.isEmpty()) {
List<List<StudentData>> partitions = ExcelImportUtil.partition(toInsert, batchSize);
for (List<StudentData> batch : partitions) {
for (StudentData sd : batch) {
try {
// 创建用户账号
User user = new User();
user.setUsername(sd.data.getStudentNo());
user.setPassword(passwordEncoder.encode(sd.password));
user.setEmail(null);
user.setRole(4); // 学生角色
user.setStatus(UserStatus.ACTIVE.getValue());
user.setCreatedTime(LocalDateTime.now());
user.setUpdatedTime(LocalDateTime.now());
userMapper.insert(user);
// 创建学生信息
Student student = new Student();
student.setUserId(user.getId());
student.setClassId(sd.classId);
student.setStudentNo(sd.data.getStudentNo());
student.setRealName(sd.data.getRealName());
student.setGender(parseGender(sd.data.getGender()));
student.setPhone(sd.data.getPhone());
student.setStatus(1);
student.setCreatedTime(LocalDateTime.now());
student.setUpdatedTime(LocalDateTime.now());
studentMapper.insert(student);
successCount++;
log.info("导入学生成功: studentNo={}, realName={}, password={}",
sd.data.getStudentNo(), sd.data.getRealName(), sd.password);
} catch (Exception e) {
log.error("导入学生失败: studentNo={}", sd.data.getStudentNo(), e);
}
}
}
}
// 4. 检查数据库冲突(学号、用户名、班级代码)
List<StudentData> toInsert = checkStudentConflictsAndBuild(validRows, errors);
// 5. 批量插入先插入User表再插入Student表
int successCount = insertStudentsWithUsers(toInsert);
log.info("学生批量导入完成: 总数={}, 成功={}, 失败={}", rows.size(), successCount, errors.size());
return ImportResultDTO.success(rows.size(), successCount, errors);
return ImportResultDTO.success(rows.size(), successCount, errors);
} catch (IOException e) {
log.error("读取Excel文件失败", e);
return ImportResultDTO.fileError("读取文件失败: " + e.getMessage());
@ -479,12 +327,246 @@ public class StudentServiceImpl implements StudentService {
}
/**
* ID
* Excel使StudentReadListener
*/
private void readStudentExcel(MultipartFile file, List<ExcelImportUtil.RowData<StudentExcelDTO>> rows,
List<ImportErrorDTO> errors) throws IOException {
StudentReadListener listener = new StudentReadListener();
ExcelImportUtil.readExcel(file, StudentExcelDTO.class, listener);
rows.addAll(listener.getRows());
errors.addAll(listener.getErrors());
}
/**
* Excel
*
*/
private List<ExcelImportUtil.RowData<StudentExcelDTO>> validateStudentData(
List<ExcelImportUtil.RowData<StudentExcelDTO>> rows, List<ImportErrorDTO> errors) {
Set<String> studentNosInExcel = new HashSet<>();
List<ExcelImportUtil.RowData<StudentExcelDTO>> validRows = new ArrayList<>();
for (ExcelImportUtil.RowData<StudentExcelDTO> row : rows) {
int rowNo = row.getRowNo();
StudentExcelDTO data = row.getData();
// 检查必填字段
if (StringUtil.isBlank(data.getStudentNo())) {
errors.add(new ImportErrorDTO(rowNo, "学号不能为空", null));
continue;
}
if (StringUtil.isBlank(data.getRealName())) {
errors.add(new ImportErrorDTO(rowNo, "真实姓名不能为空", null));
continue;
}
if (StringUtil.isBlank(data.getClassCode())) {
errors.add(new ImportErrorDTO(rowNo, "班级代码不能为空", null));
continue;
}
// 检查Excel内学号重复
if (!studentNosInExcel.add(data.getStudentNo())) {
errors.add(new ImportErrorDTO(rowNo, "Excel内学号重复: " + data.getStudentNo(), null));
continue;
}
validRows.add(row);
}
return validRows;
}
/**
*
* StudentUser
*/
private List<StudentData> checkStudentConflictsAndBuild(
List<ExcelImportUtil.RowData<StudentExcelDTO>> validRows, List<ImportErrorDTO> errors) {
List<StudentData> toInsert = new ArrayList<>();
// 收集需要检查的学号和班级代码
List<String> studentNos = validRows.stream()
.map(r -> r.getData().getStudentNo()).distinct().toList();
List<String> classCodes = validRows.stream()
.map(r -> r.getData().getClassCode()).distinct().toList();
// 批量查询数据库进行冲突检查
Set<String> dbExistStudentNos = queryExistingStudentNos(studentNos);
Set<String> dbExistUsernames = queryExistingUsernames(studentNos);
Map<String, Classes> classMap = queryClassesByCode(classCodes);
// 逐条检查冲突并构建待插入数据
for (ExcelImportUtil.RowData<StudentExcelDTO> row : validRows) {
int rowNo = row.getRowNo();
StudentExcelDTO data = row.getData();
// 检查学号和用户名冲突
if (dbExistStudentNos.contains(data.getStudentNo())) {
errors.add(new ImportErrorDTO(rowNo, "学号已存在: " + data.getStudentNo(), null));
continue;
}
if (dbExistUsernames.contains(data.getStudentNo())) {
errors.add(new ImportErrorDTO(rowNo, "用户名已存在: " + data.getStudentNo(), null));
continue;
}
// 检查班级代码存在性
Classes classes = classMap.get(data.getClassCode());
if (classes == null) {
errors.add(new ImportErrorDTO(rowNo, "班级代码不存在: " + data.getClassCode(), null));
continue;
}
// 构建待插入数据包含班级ID
StudentData studentData = new StudentData();
studentData.data = data;
studentData.classId = classes.getId();
toInsert.add(studentData);
}
return toInsert;
}
/**
*
*/
private Set<String> queryExistingStudentNos(List<String> studentNos) {
return new HashSet<>(
studentMapper.selectList(new LambdaQueryWrapper<Student>().in(Student::getStudentNo, studentNos))
.stream().map(Student::getStudentNo).toList()
);
}
/**
*
*/
private Set<String> queryExistingUsernames(List<String> studentNos) {
return new HashSet<>(
userMapper.selectList(new LambdaQueryWrapper<User>().in(User::getUsername, studentNos))
.stream().map(User::getUsername).toList()
);
}
/**
*
*/
private Map<String, Classes> queryClassesByCode(List<String> classCodes) {
return classesMapper.selectList(
new LambdaQueryWrapper<Classes>().in(Classes::getClassCode, classCodes)
).stream().collect(Collectors.toMap(Classes::getClassCode, c -> c));
}
/**
*
* UseruserIdStudent
* UserStudentuserId
*/
private int insertStudentsWithUsers(List<StudentData> toInsert) {
if (toInsert.isEmpty()) return 0;
// 1. 构建并批量插入User对象密码默认为学号
List<User> usersToInsert = buildUsersForStudents(toInsert);
batchInsertUsers(usersToInsert);
// 2. 查询插入后的User获取userId映射
List<String> insertedStudentNos = toInsert.stream()
.map(sd -> sd.data.getStudentNo()).toList();
Map<String, String> userIdMap = queryUserIdsByStudentNos(insertedStudentNos);
// 3. 构建并批量插入Student对象
List<Student> studentsToInsert = buildStudentsWithUserIds(toInsert, userIdMap);
batchInsertStudents(studentsToInsert);
return studentsToInsert.size();
}
/**
* User
* ===
*/
private List<User> buildUsersForStudents(List<StudentData> toInsert) {
return toInsert.stream().map(sd -> {
User user = new User();
user.setUsername(sd.data.getStudentNo());
user.setPassword(passwordEncoder.encode(sd.data.getStudentNo()));
user.setEmail(null);
user.setRole(4); // 学生角色
user.setStatus(UserStatus.ACTIVE.getValue());
user.setCreatedTime(LocalDateTime.now());
user.setUpdatedTime(LocalDateTime.now());
return user;
}).toList();
}
/**
* User
*/
private void batchInsertUsers(List<User> users) {
List<List<User>> partitions = ExcelImportUtil.partition(users, batchSize);
for (List<User> batch : partitions) {
userMapper.insert(batch);
}
}
/**
* UserID userId
*/
private Map<String, String> queryUserIdsByStudentNos(List<String> studentNos) {
return userMapper.selectList(new LambdaQueryWrapper<User>().in(User::getUsername, studentNos))
.stream().collect(Collectors.toMap(User::getUsername, User::getId));
}
/**
* Student使userIdUser
*/
private List<Student> buildStudentsWithUserIds(List<StudentData> toInsert, Map<String, String> userIdMap) {
return toInsert.stream()
.map(sd -> {
String userId = userIdMap.get(sd.data.getStudentNo());
if (userId == null) return null;
Student student = new Student();
student.setUserId(userId);
student.setClassId(sd.classId);
student.setStudentNo(sd.data.getStudentNo());
student.setRealName(sd.data.getRealName());
student.setGender(parseGender(sd.data.getGender()));
student.setPhone(sd.data.getPhone());
student.setStatus(EntityStatus.ACTIVE.getValue());
student.setCreatedTime(LocalDateTime.now());
student.setUpdatedTime(LocalDateTime.now());
return student;
})
.filter(Objects::nonNull)
.toList();
}
/**
* Student
*/
private void batchInsertStudents(List<Student> students) {
List<List<Student>> partitions = ExcelImportUtil.partition(students, batchSize);
for (List<Student> batch : partitions) {
studentMapper.insert(batch);
}
}
/**
*
*/
@Override
public StudentDetailDTO getStudentDetail(String studentId) {
StudentDetailDTO studentDetail = studentMapper.getStudentAndClassDetail(studentId);
if(studentDetail == null) {
throw new ServiceException("学生不存在");
}
return studentDetail;
}
/**
* ID
*/
private static class StudentData {
StudentExcelDTO data;
String classId;
String password;
}
/**

@ -13,8 +13,10 @@ import org.springframework.web.multipart.MultipartFile;
import yuxingshi.sms.common.core.domain.dto.BasePageDTO;
import yuxingshi.sms.common.core.domain.dto.excel.ImportErrorDTO;
import yuxingshi.sms.common.core.domain.dto.excel.ImportResultDTO;
import yuxingshi.sms.common.core.domain.enums.EntityStatus;
import yuxingshi.sms.common.core.domain.enums.UserStatus;
import yuxingshi.sms.common.core.exception.ServiceException;
import yuxingshi.sms.common.core.utils.BeanCopyUtil;
import yuxingshi.sms.common.core.utils.ExcelImportUtil;
import yuxingshi.sms.common.core.utils.StringUtil;
import yuxingshi.sms.server.domain.dto.teacher.TeacherExcelDTO;
@ -24,11 +26,9 @@ import yuxingshi.sms.server.domain.dto.teacher.TeacherReadListener;
import yuxingshi.sms.server.domain.dto.teacher.TeacherRespDTO;
import yuxingshi.sms.server.domain.entity.Teacher;
import yuxingshi.sms.server.domain.entity.User;
import yuxingshi.sms.server.mapper.CourseMapper;
import yuxingshi.sms.server.mapper.TeacherMapper;
import yuxingshi.sms.server.mapper.UserMapper;
import yuxingshi.sms.server.service.TeacherService;
import yuxingshi.sms.server.utils.PasswordGeneratorUtil;
import java.io.IOException;
import java.time.LocalDateTime;
@ -45,8 +45,6 @@ public class TeacherServiceImpl implements TeacherService {
@Autowired
private UserMapper userMapper;
@Autowired
private CourseMapper courseMapper;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@ -66,13 +64,10 @@ public class TeacherServiceImpl implements TeacherService {
throw new ServiceException("教师工号已存在");
}
// 生成随机密码
String randomPassword = PasswordGeneratorUtil.generatePassword();
// 创建用户账号
// 创建用户账号,密码默认为工号
User user = new User();
user.setUsername(dto.getTeacherNo());
user.setPassword(passwordEncoder.encode(randomPassword));
user.setPassword(passwordEncoder.encode(dto.getTeacherNo()));
user.setEmail(null);
user.setRole(3); // 教师角色
user.setStatus(UserStatus.ACTIVE.getValue());
@ -87,12 +82,12 @@ public class TeacherServiceImpl implements TeacherService {
teacher.setRealName(dto.getRealName());
teacher.setDepartment(dto.getDepartment());
teacher.setPhone(dto.getPhone());
teacher.setStatus(1);
teacher.setStatus(EntityStatus.ACTIVE.getValue());
teacher.setCreatedTime(LocalDateTime.now());
teacher.setUpdatedTime(LocalDateTime.now());
teacherMapper.insert(teacher);
log.info("创建教师成功: teacherNo={}, realName={}, password={}", dto.getTeacherNo(), dto.getRealName(), randomPassword);
log.info("创建教师成功: teacherNo={}, realName={}", dto.getTeacherNo(), dto.getRealName());
return teacher.getId();
}
@ -139,14 +134,6 @@ public class TeacherServiceImpl implements TeacherService {
throw new ServiceException("教师不存在");
}
// 检查是否有关联的课程
LambdaQueryWrapper<yuxingshi.sms.server.domain.entity.Course> courseQuery = new LambdaQueryWrapper<>();
courseQuery.eq(yuxingshi.sms.server.domain.entity.Course::getTeacherId, teacherId);
long courseCount = courseMapper.selectCount(courseQuery);
if (courseCount > 0) {
throw new ServiceException("教师关联了课程,无法删除。请先删除相关课程");
}
// 删除教师记录
teacherMapper.deleteById(teacherId);
@ -182,7 +169,7 @@ public class TeacherServiceImpl implements TeacherService {
// 更新教师状态
LambdaUpdateWrapper<Teacher> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Teacher::getId, teacherId)
.set(Teacher::getStatus, 0)
.set(Teacher::getStatus, EntityStatus.DISABLED.getValue())
.set(Teacher::getUpdatedTime, LocalDateTime.now());
teacherMapper.update(null, updateWrapper);
@ -209,7 +196,7 @@ public class TeacherServiceImpl implements TeacherService {
// 更新教师状态
LambdaUpdateWrapper<Teacher> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Teacher::getId, teacherId)
.set(Teacher::getStatus, 1)
.set(Teacher::getStatus, EntityStatus.ACTIVE.getValue())
.set(Teacher::getUpdatedTime, LocalDateTime.now());
teacherMapper.update(null, updateWrapper);
@ -278,154 +265,41 @@ public class TeacherServiceImpl implements TeacherService {
/**
*
* @param file Excel
* @return
* Excel
* UserTeacher使<EFBFBD><EFBFBD>
*/
@Transactional
@Override
public ImportResultDTO importTeachers(MultipartFile file) {
// 1. 文件校验
// 1. 文件格式和大小校验
ImportResultDTO validateResult = ExcelImportUtil.validateFile(file, maxFileSize);
if (validateResult != null) {
return validateResult;
}
if (validateResult != null) return validateResult;
try {
// 2. 读取Excel
TeacherReadListener listener = new TeacherReadListener();
ExcelImportUtil.readExcel(file, TeacherExcelDTO.class, listener);
// 2. 读取Excel数据
List<ExcelImportUtil.RowData<TeacherExcelDTO>> rows = new ArrayList<>();
List<ImportErrorDTO> errors = new ArrayList<>();
readTeacherExcel(file, rows, errors);
List<ExcelImportUtil.RowData<TeacherExcelDTO>> rows = listener.getRows();
List<ImportErrorDTO> errors = new ArrayList<>(listener.getErrors());
if (rows.isEmpty()) {
return ImportResultDTO.emptyData(errors);
}
// 3. 数据校验:必填字段 + Excel内去重
Set<String> teacherNosInExcel = new HashSet<>();
List<ExcelImportUtil.RowData<TeacherExcelDTO>> validRows = new ArrayList<>();
for (ExcelImportUtil.RowData<TeacherExcelDTO> row : rows) {
int rowNo = row.getRowNo();
TeacherExcelDTO data = row.getData();
String teacherNo = data.getTeacherNo();
String realName = data.getRealName();
// 校验必填字段
if (StringUtil.isBlank(teacherNo)) {
errors.add(new ImportErrorDTO(rowNo, "教师工号不能为空", null));
continue;
}
if (StringUtil.isBlank(realName)) {
errors.add(new ImportErrorDTO(rowNo, "教师姓名不能为空", null));
continue;
}
// Excel内去重
if (!teacherNosInExcel.add(teacherNo)) {
errors.add(new ImportErrorDTO(rowNo, "Excel内教师工号重复: " + teacherNo, null));
continue;
}
validRows.add(row);
}
if (rows.isEmpty()) return ImportResultDTO.emptyData(errors);
// 3. 校验必填字段和Excel内数据重复性
List<ExcelImportUtil.RowData<TeacherExcelDTO>> validRows = validateTeacherData(rows, errors);
if (validRows.isEmpty()) {
return ImportResultDTO.builder()
.success(false)
.totalCount(rows.size())
.successCount(0)
.failureCount(errors.size())
.errors(errors)
.message("没有可导入的有效数据")
.build();
.success(false).totalCount(rows.size()).successCount(0)
.failureCount(errors.size()).errors(errors)
.message("没有可导入的有效数据").build();
}
// 4. 数据库去重校验(教师工号)
List<String> teacherNos = validRows.stream()
.map(r -> r.getData().getTeacherNo())
.distinct()
.toList();
Set<String> dbExistTeacherNos = new HashSet<>(
teacherMapper.selectList(new LambdaQueryWrapper<Teacher>().in(Teacher::getTeacherNo, teacherNos))
.stream()
.map(Teacher::getTeacherNo)
.toList()
);
// 5. 校验用户名是否已存在
Set<String> dbExistUsernames = new HashSet<>(
userMapper.selectList(new LambdaQueryWrapper<User>().in(User::getUsername, teacherNos))
.stream()
.map(User::getUsername)
.toList()
);
// 6. 构建待插入数据
List<TeacherData> toInsert = new ArrayList<>();
for (ExcelImportUtil.RowData<TeacherExcelDTO> row : validRows) {
int rowNo = row.getRowNo();
TeacherExcelDTO data = row.getData();
if (dbExistTeacherNos.contains(data.getTeacherNo())) {
errors.add(new ImportErrorDTO(rowNo, "教师工号已存在: " + data.getTeacherNo(), null));
continue;
}
if (dbExistUsernames.contains(data.getTeacherNo())) {
errors.add(new ImportErrorDTO(rowNo, "用户名已存在: " + data.getTeacherNo(), null));
continue;
}
TeacherData teacherData = new TeacherData();
teacherData.data = data;
teacherData.password = PasswordGeneratorUtil.generatePassword();
toInsert.add(teacherData);
}
// 7. 批量插入(需要先创建用户,再创建教师)
int successCount = 0;
if (!toInsert.isEmpty()) {
List<List<TeacherData>> partitions = ExcelImportUtil.partition(toInsert, batchSize);
for (List<TeacherData> batch : partitions) {
for (TeacherData td : batch) {
try {
// 创建用户账号
User user = new User();
user.setUsername(td.data.getTeacherNo());
user.setPassword(passwordEncoder.encode(td.password));
user.setEmail(null);
user.setRole(3); // 教师角色
user.setStatus(UserStatus.ACTIVE.getValue());
user.setCreatedTime(LocalDateTime.now());
user.setUpdatedTime(LocalDateTime.now());
userMapper.insert(user);
// 创建教师信息
Teacher teacher = new Teacher();
teacher.setUserId(user.getId());
teacher.setTeacherNo(td.data.getTeacherNo());
teacher.setRealName(td.data.getRealName());
teacher.setDepartment(td.data.getDepartment());
teacher.setPhone(td.data.getPhone());
teacher.setStatus(1);
teacher.setCreatedTime(LocalDateTime.now());
teacher.setUpdatedTime(LocalDateTime.now());
teacherMapper.insert(teacher);
successCount++;
log.info("导入教师成功: teacherNo={}, realName={}, password={}",
td.data.getTeacherNo(), td.data.getRealName(), td.password);
} catch (Exception e) {
log.error("导入教师失败: teacherNo={}", td.data.getTeacherNo(), e);
}
}
}
}
// 4. 检查数据库冲突(工号、用户名)
List<TeacherData> toInsert = checkTeacherConflictsAndBuild(validRows, errors);
// 5. 批量插入先插入User表再插入Teacher表
int successCount = insertTeachersWithUsers(toInsert);
log.info("教师批量导入完成: 总数={}, 成功={}, 失败={}", rows.size(), successCount, errors.size());
return ImportResultDTO.success(rows.size(), successCount, errors);
return ImportResultDTO.success(rows.size(), successCount, errors);
} catch (IOException e) {
log.error("读取Excel文件失败", e);
return ImportResultDTO.fileError("读取文件失败: " + e.getMessage());
@ -433,10 +307,200 @@ public class TeacherServiceImpl implements TeacherService {
}
/**
*
* Excel使TeacherReadListener
*/
private void readTeacherExcel(MultipartFile file, List<ExcelImportUtil.RowData<TeacherExcelDTO>> rows,
List<ImportErrorDTO> errors) throws IOException {
TeacherReadListener listener = new TeacherReadListener();
ExcelImportUtil.readExcel(file, TeacherExcelDTO.class, listener);
rows.addAll(listener.getRows());
errors.addAll(listener.getErrors());
}
/**
* Excel
*
*/
private List<ExcelImportUtil.RowData<TeacherExcelDTO>> validateTeacherData(
List<ExcelImportUtil.RowData<TeacherExcelDTO>> rows, List<ImportErrorDTO> errors) {
Set<String> teacherNosInExcel = new HashSet<>();
List<ExcelImportUtil.RowData<TeacherExcelDTO>> validRows = new ArrayList<>();
for (ExcelImportUtil.RowData<TeacherExcelDTO> row : rows) {
int rowNo = row.getRowNo();
TeacherExcelDTO data = row.getData();
// 检查必填字段
if (StringUtil.isBlank(data.getTeacherNo())) {
errors.add(new ImportErrorDTO(rowNo, "教师工号不能为空", null));
continue;
}
if (StringUtil.isBlank(data.getRealName())) {
errors.add(new ImportErrorDTO(rowNo, "教师姓名不能为空", null));
continue;
}
// 检查Excel内工号重复
if (!teacherNosInExcel.add(data.getTeacherNo())) {
errors.add(new ImportErrorDTO(rowNo, "Excel内教师工号重复: " + data.getTeacherNo(), null));
continue;
}
validRows.add(row);
}
return validRows;
}
/**
*
* TeacherUser
*/
private List<TeacherData> checkTeacherConflictsAndBuild(
List<ExcelImportUtil.RowData<TeacherExcelDTO>> validRows, List<ImportErrorDTO> errors) {
List<TeacherData> toInsert = new ArrayList<>();
List<String> teacherNos = validRows.stream()
.map(r -> r.getData().getTeacherNo()).distinct().toList();
// 批量查询数据库进行冲突检查
Set<String> dbExistTeacherNos = new HashSet<>(
teacherMapper.selectList(new LambdaQueryWrapper<Teacher>().in(Teacher::getTeacherNo, teacherNos))
.stream().map(Teacher::getTeacherNo).toList());
Set<String> dbExistUsernames = new HashSet<>(
userMapper.selectList(new LambdaQueryWrapper<User>().in(User::getUsername, teacherNos))
.stream().map(User::getUsername).toList());
// 逐条检查冲突并构建待插入数据
for (ExcelImportUtil.RowData<TeacherExcelDTO> row : validRows) {
TeacherExcelDTO data = row.getData();
int rowNo = row.getRowNo();
// 检查工号和用户名冲突
if (dbExistTeacherNos.contains(data.getTeacherNo())) {
errors.add(new ImportErrorDTO(rowNo, "教师工号已存在: " + data.getTeacherNo(), null));
continue;
}
if (dbExistUsernames.contains(data.getTeacherNo())) {
errors.add(new ImportErrorDTO(rowNo, "用户名已存在: " + data.getTeacherNo(), null));
continue;
}
// 构建待插入数据
TeacherData teacherData = new TeacherData();
teacherData.data = data;
toInsert.add(teacherData);
}
return toInsert;
}
/**
*
* UseruserIdTeacher
* UserTeacheruserId
*/
private int insertTeachersWithUsers(List<TeacherData> toInsert) {
if (toInsert.isEmpty()) return 0;
// 1. 构建并批量插入User对象密码默认为工号
List<User> usersToInsert = buildUsersForTeachers(toInsert);
batchInsertUsers(usersToInsert);
// 2. 查询插入后的User获取userId映射
List<String> insertedTeacherNos = toInsert.stream()
.map(td -> td.data.getTeacherNo()).toList();
Map<String, String> userIdMap = queryUserIdsByTeacherNos(insertedTeacherNos);
// 3. 构建并批量插入Teacher对象
List<Teacher> teachersToInsert = buildTeachersWithUserIds(toInsert, userIdMap);
batchInsertTeachers(teachersToInsert);
return teachersToInsert.size();
}
/**
* User
* ===
*/
private List<User> buildUsersForTeachers(List<TeacherData> toInsert) {
return toInsert.stream().map(td -> {
User user = new User();
user.setUsername(td.data.getTeacherNo());
user.setPassword(passwordEncoder.encode(td.data.getTeacherNo()));
user.setEmail(null);
user.setRole(3); // 教师角色
user.setStatus(UserStatus.ACTIVE.getValue());
user.setCreatedTime(LocalDateTime.now());
user.setUpdatedTime(LocalDateTime.now());
return user;
}).toList();
}
/**
* User
*/
private void batchInsertUsers(List<User> users) {
List<List<User>> partitions = ExcelImportUtil.partition(users, batchSize);
for (List<User> batch : partitions) {
userMapper.insert(batch);
}
}
/**
* UserID userId
*/
private Map<String, String> queryUserIdsByTeacherNos(List<String> teacherNos) {
return userMapper.selectList(new LambdaQueryWrapper<User>().in(User::getUsername, teacherNos))
.stream().collect(Collectors.toMap(User::getUsername, User::getId));
}
/**
* Teacher使userIdUser
*/
private List<Teacher> buildTeachersWithUserIds(List<TeacherData> toInsert, Map<String, String> userIdMap) {
return toInsert.stream()
.map(td -> {
String userId = userIdMap.get(td.data.getTeacherNo());
if (userId == null) return null;
Teacher teacher = new Teacher();
teacher.setUserId(userId);
teacher.setTeacherNo(td.data.getTeacherNo());
teacher.setRealName(td.data.getRealName());
teacher.setDepartment(td.data.getDepartment());
teacher.setPhone(td.data.getPhone());
teacher.setStatus(EntityStatus.ACTIVE.getValue());
teacher.setCreatedTime(LocalDateTime.now());
teacher.setUpdatedTime(LocalDateTime.now());
return teacher;
})
.filter(Objects::nonNull)
.toList();
}
/**
* Teacher
*/
private void batchInsertTeachers(List<Teacher> teachers) {
List<List<Teacher>> partitions = ExcelImportUtil.partition(teachers, batchSize);
for (List<Teacher> batch : partitions) {
teacherMapper.insert(batch);
}
}
/**
*
*/
@Override
public TeacherRespDTO getTeacherDetail(String teacherId) {
Teacher teacher = teacherMapper.selectOne(new LambdaQueryWrapper<Teacher>()
.eq(Teacher::getId, teacherId));
TeacherRespDTO teacherRespDTO = new TeacherRespDTO();
BeanCopyUtil.copyProperties(teacher, teacherRespDTO);
return teacherRespDTO;
}
/**
*
*/
private static class TeacherData {
TeacherExcelDTO data;
String password;
}
}

Loading…
Cancel
Save