ClassServiceImpl.java 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. package com.njuzr.eaibackend.service.impl;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.njuzr.eaibackend.dto.ClassDTO;
  4. import com.njuzr.eaibackend.enums.ClassStudentState;
  5. import com.njuzr.eaibackend.enums.Role;
  6. import com.njuzr.eaibackend.exception.MyException;
  7. import com.njuzr.eaibackend.mapper.*;
  8. import com.njuzr.eaibackend.po.Class;
  9. import com.njuzr.eaibackend.po.ClassStudent;
  10. import com.njuzr.eaibackend.po.Enrollment;
  11. import com.njuzr.eaibackend.po.User;
  12. import com.njuzr.eaibackend.service.ClassService;
  13. import com.njuzr.eaibackend.utils.WebClientUtil;
  14. import com.njuzr.eaibackend.vo.BatchStudentImportDetailVO;
  15. import com.njuzr.eaibackend.vo.BatchStudentImportResultVO;
  16. import com.njuzr.eaibackend.vo.StudentInfoVO;
  17. import com.njuzr.eaibackend.vo.ClassVO;
  18. import com.opencsv.CSVReader;
  19. import com.opencsv.exceptions.CsvValidationException;
  20. import lombok.extern.slf4j.Slf4j;
  21. import org.springframework.beans.factory.annotation.Value;
  22. import org.apache.poi.ss.usermodel.*;
  23. import org.apache.poi.xssf.usermodel.XSSFWorkbook;
  24. import org.springframework.http.HttpStatus;
  25. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  26. import org.springframework.security.crypto.password.PasswordEncoder;
  27. import org.springframework.stereotype.Service;
  28. import org.springframework.transaction.annotation.Transactional;
  29. import org.springframework.web.multipart.MultipartFile;
  30. import java.io.IOException;
  31. import java.io.InputStreamReader;
  32. import java.math.BigDecimal;
  33. import java.util.ArrayList;
  34. import java.util.Date;
  35. import java.util.HashMap;
  36. import java.util.HashSet;
  37. import java.util.List;
  38. import java.util.Map;
  39. import java.util.Random;
  40. import java.util.Set;
  41. import java.util.regex.Pattern;
  42. import java.util.stream.Collectors;
  43. import org.springframework.beans.BeanUtils;
  44. @Slf4j
  45. @Service
  46. public class ClassServiceImpl implements ClassService {
  47. private final ClassMapper classMapper;
  48. private final ClassStudentMapper classStudentMapper;
  49. private final UserMapper userMapper;
  50. private final CourseStudentMapper courseStudentMapper;
  51. private final WebClientUtil webClientUtil;
  52. @Value("${portal.base-url:https://p-nju.seec.seecoder.cn}")
  53. private String portalBaseUrl;
  54. @Value("${portal.batch-register-path:/api/user/register/batch/internal}")
  55. private String portalBatchRegisterPath;
  56. private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
  57. public ClassServiceImpl(ClassMapper classMapper,
  58. ClassStudentMapper classStudentMapper,
  59. UserMapper userMapper,
  60. CourseStudentMapper courseStudentMapper,
  61. WebClientUtil webClientUtil) {
  62. this.classMapper = classMapper;
  63. this.classStudentMapper = classStudentMapper;
  64. this.userMapper = userMapper;
  65. this.courseStudentMapper = courseStudentMapper;
  66. this.webClientUtil = webClientUtil;
  67. }
  68. @Override
  69. public ClassVO createClass(ClassDTO classDTO) {
  70. Class aClass = new Class();
  71. aClass.setClassName(classDTO.getClassName());
  72. aClass.setCourseId(classDTO.getCourseId());
  73. aClass.setClassTime(classDTO.getClassTime());
  74. aClass.setTeacherId(classDTO.getTeacherId());
  75. aClass.setStuNumber(0); // 初始人数为0
  76. // 生成班级码
  77. String classCode = generateClassCode();
  78. aClass.setClassCode(classCode);
  79. int status = classMapper.insert(aClass);
  80. if (status == 0) {
  81. log.error("创建班级失败,数据库插入错误!");
  82. throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()+":"+"创建班级错误");
  83. }
  84. return convertToVO(aClass);
  85. }
  86. public String generateClassCode() {
  87. // 生成6位字母数字组合的班级码
  88. String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  89. Random random = new Random();
  90. StringBuilder codeBuilder = new StringBuilder();
  91. for (int i = 0; i < 6; i++) {
  92. int index = random.nextInt(characters.length());
  93. codeBuilder.append(characters.charAt(index));
  94. }
  95. String classCode = codeBuilder.toString();
  96. // 检查唯一性,如果不唯一则重新生成
  97. if (!isClassCodeUnique(classCode)) {
  98. return generateClassCode();
  99. }
  100. return classCode;
  101. }
  102. public boolean isClassCodeUnique(String classCode) {
  103. QueryWrapper<Class> wrapper = new QueryWrapper<>();
  104. wrapper.eq("class_code", classCode);
  105. Class existingClass = classMapper.selectOne(wrapper);
  106. return existingClass == null;
  107. }
  108. private ClassVO convertToVO(Class aClass) {
  109. ClassVO classVO = new ClassVO();
  110. BeanUtils.copyProperties(aClass, classVO);
  111. return classVO;
  112. }
  113. @Override
  114. public Class updateClass(ClassDTO classDTO) {
  115. Class aClass=new Class();
  116. aClass.setClassName(classDTO.getClassName());
  117. aClass.setClassId(classDTO.getClassId());
  118. aClass.setClassTime(classDTO.getClassTime());
  119. try {
  120. int code = classMapper.updateById(aClass);
  121. if (code == 0) {
  122. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新班级失败");
  123. }
  124. } catch (Exception e) {
  125. log.error(e.getMessage());
  126. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新班级失败");
  127. }
  128. aClass= classMapper.selectById(classDTO.getClassId());
  129. return aClass;
  130. }
  131. @Override
  132. public List<Class> select(Long courseId) {
  133. QueryWrapper<Class> queryWrapper = new QueryWrapper<>();
  134. queryWrapper.eq("course_id", courseId); // 按course_id查询
  135. List<Class> res= classMapper.selectList(queryWrapper);
  136. return res;
  137. }
  138. @Override
  139. @Transactional
  140. public String deleteClassWithCheck(Long classId) {
  141. // 1. 查询班级信息
  142. Class cls = classMapper.selectById(classId);
  143. if (cls == null) {
  144. return "班级不存在";
  145. }
  146. // 2. 检查学生数量
  147. if (cls.getStuNumber() > 0) {
  148. return "班级中还有学生,无法删除";
  149. }
  150. // 3. 使用Mapper执行带条件的删除
  151. QueryWrapper<Class> deleteWrapper = new QueryWrapper<>();
  152. deleteWrapper.eq("class_id", classId)
  153. .eq("stu_number", 0); // 双重检查
  154. int affectedRows = classMapper.delete(deleteWrapper);
  155. if (affectedRows == 0) {
  156. return "删除失败(可能学生数量已变化)";
  157. }
  158. return "删除成功";
  159. }
  160. @Override
  161. @Transactional
  162. public BatchStudentImportResultVO batchAddStudents(Long classId, MultipartFile file, String accessToken) {
  163. // 检查班级是否存在
  164. Class cls = classMapper.selectById(classId);
  165. if (cls == null) {
  166. throw new MyException(404, "班级不存在");
  167. }
  168. // 解析文件
  169. List<StudentImportRow> rows;
  170. String fileName = file.getOriginalFilename();
  171. try {
  172. if (fileName != null && (fileName.endsWith(".xls") || fileName.endsWith(".xlsx"))) {
  173. rows = parseExcelFile(file);
  174. } else if (fileName != null && fileName.endsWith(".csv")) {
  175. rows = parseCsvFile(file);
  176. } else {
  177. throw new MyException(400, "不支持的文件类型");
  178. }
  179. } catch (Exception e) {
  180. log.error("解析学生文件失败", e);
  181. throw new MyException(500, "解析学生文件失败: " + e.getMessage());
  182. }
  183. BatchStudentImportResultVO result = new BatchStudentImportResultVO();
  184. result.setTotal(rows.size());
  185. result.setSuccessCount(0);
  186. result.setSkippedCount(0);
  187. result.setFailedCount(0);
  188. if (rows.isEmpty()) {
  189. return result;
  190. }
  191. Set<String> fileOfficialNumbers = new HashSet<>();
  192. Set<String> filePhones = new HashSet<>();
  193. List<StudentImportRow> rowsToRegister = new ArrayList<>();
  194. Map<Integer, BatchStudentImportDetailVO> pendingRegisterDetails = new HashMap<>();
  195. List<User> usersToInsert = new ArrayList<>();
  196. List<ClassStudent> studentsToInsert = new ArrayList<>();
  197. Set<Long> enrollmentStudentIds = new HashSet<>();
  198. for (StudentImportRow row : rows) {
  199. BatchStudentImportDetailVO detail = new BatchStudentImportDetailVO();
  200. detail.setRowIndex(row.getRowIndex());
  201. detail.setOfficialNumber(row.getOfficialNumber());
  202. detail.setStuName(row.getStuName());
  203. detail.setPhone(row.getPhone());
  204. result.getDetails().add(detail);
  205. String validationError = validateRow(row, fileOfficialNumbers, filePhones);
  206. if (validationError != null) {
  207. detail.setStatus("FAILED");
  208. detail.setMessage(validationError);
  209. result.setFailedCount(result.getFailedCount() + 1);
  210. continue;
  211. }
  212. User existsByNumber = userMapper.selectByOfficialNumber(row.getOfficialNumber());
  213. QueryWrapper<ClassStudent> classStudentWrapper = new QueryWrapper<>();
  214. classStudentWrapper.eq("class_id", classId)
  215. .eq("official_number", row.getOfficialNumber());
  216. if (classStudentMapper.selectCount(classStudentWrapper) > 0) {
  217. detail.setStatus("SKIPPED");
  218. detail.setMessage("学生已在班级中,已跳过");
  219. result.setSkippedCount(result.getSkippedCount() + 1);
  220. continue;
  221. }
  222. if (existsByNumber != null) {
  223. ClassStudent student = new ClassStudent();
  224. student.setClassId(classId);
  225. student.setOfficialNumber(row.getOfficialNumber());
  226. student.setStuName(row.getStuName());
  227. student.setState(ClassStudentState.NOT_JOINED);
  228. studentsToInsert.add(student);
  229. enrollmentStudentIds.add(existsByNumber.getId());
  230. detail.setStatus("SUCCESS");
  231. detail.setMessage("学生已存在,已关联班级和课程");
  232. result.setSuccessCount(result.getSuccessCount() + 1);
  233. fileOfficialNumbers.add(row.getOfficialNumber());
  234. filePhones.add(row.getPhone());
  235. continue;
  236. }
  237. User existsByPhone = userMapper.selectByPhone(row.getPhone());
  238. if (existsByPhone != null) {
  239. detail.setStatus("SKIPPED");
  240. detail.setMessage("EAI中手机号已存在,已跳过");
  241. result.setSkippedCount(result.getSkippedCount() + 1);
  242. continue;
  243. }
  244. rowsToRegister.add(row);
  245. pendingRegisterDetails.put(row.getRowIndex(), detail);
  246. fileOfficialNumbers.add(row.getOfficialNumber());
  247. filePhones.add(row.getPhone());
  248. }
  249. if (!rowsToRegister.isEmpty()) {
  250. Map<Integer, Map<String, Object>> portalResultByRow = registerUsersInPortal(rowsToRegister, accessToken);
  251. for (StudentImportRow row : rowsToRegister) {
  252. BatchStudentImportDetailVO detail = pendingRegisterDetails.get(row.getRowIndex());
  253. Map<String, Object> portalRow = portalResultByRow.get(row.getRowIndex());
  254. if (portalRow == null || !"SUCCESS".equals(String.valueOf(portalRow.get("status")))) {
  255. detail.setStatus("SKIPPED");
  256. detail.setMessage(portalRow == null ? "Portal未返回该行处理结果" : String.valueOf(portalRow.get("message")));
  257. result.setSkippedCount(result.getSkippedCount() + 1);
  258. continue;
  259. }
  260. User user = new User();
  261. user.setName(row.getStuName());
  262. user.setOfficialNumber(row.getOfficialNumber());
  263. user.setPhone(row.getPhone());
  264. user.setRole(Role.STUDENT);
  265. user.setCreateTime(new Date());
  266. Object portalUserId = portalRow.get("userId");
  267. if (portalUserId instanceof Number) {
  268. user.setPid(String.valueOf(((Number) portalUserId).longValue()));
  269. }
  270. user.setPassword(passwordEncoder.encode(generateDefaultPassword(row.getOfficialNumber())));
  271. usersToInsert.add(user);
  272. ClassStudent student = new ClassStudent();
  273. student.setClassId(classId);
  274. student.setOfficialNumber(row.getOfficialNumber());
  275. student.setStuName(row.getStuName());
  276. student.setState(ClassStudentState.NOT_JOINED);
  277. studentsToInsert.add(student);
  278. detail.setStatus("SUCCESS");
  279. detail.setMessage("导入成功");
  280. result.setSuccessCount(result.getSuccessCount() + 1);
  281. }
  282. }
  283. if (!usersToInsert.isEmpty()) {
  284. userMapper.batchInsert(usersToInsert);
  285. List<String> importedOfficialNumbers = usersToInsert.stream()
  286. .map(User::getOfficialNumber)
  287. .collect(Collectors.toList());
  288. List<User> importedUsers = userMapper.selectUserIdByOfficialNumbers(importedOfficialNumbers);
  289. for (User importedUser : importedUsers) {
  290. enrollmentStudentIds.add(importedUser.getId());
  291. }
  292. }
  293. if (!studentsToInsert.isEmpty()) {
  294. classStudentMapper.batchInsert(studentsToInsert);
  295. classMapper.increaseStuNumber(classId, studentsToInsert.size());
  296. }
  297. for (Long studentId : enrollmentStudentIds) {
  298. QueryWrapper<Enrollment> enrollmentWrapper = new QueryWrapper<>();
  299. enrollmentWrapper.eq("course_id", cls.getCourseId())
  300. .eq("student_id", studentId);
  301. if (!courseStudentMapper.exists(enrollmentWrapper)) {
  302. Enrollment enrollment = new Enrollment();
  303. enrollment.setCourseId(cls.getCourseId());
  304. enrollment.setStudentId(studentId);
  305. courseStudentMapper.insert(enrollment);
  306. }
  307. }
  308. return result;
  309. }
  310. @SuppressWarnings("unchecked")
  311. private Map<Integer, Map<String, Object>> registerUsersInPortal(List<StudentImportRow> rows, String accessToken) {
  312. String endpoint = portalBaseUrl + portalBatchRegisterPath;
  313. if (accessToken == null || accessToken.trim().isEmpty()) {
  314. throw new MyException(401, "缺少访问令牌");
  315. }
  316. List<Map<String, Object>> users = new ArrayList<>();
  317. for (StudentImportRow row : rows) {
  318. Map<String, Object> user = new HashMap<>();
  319. user.put("rowIndex", row.getRowIndex());
  320. user.put("username", row.getOfficialNumber());
  321. user.put("name", row.getStuName());
  322. user.put("phone", row.getPhone());
  323. user.put("role", "STUDENT");
  324. users.add(user);
  325. }
  326. Map<String, Object> request = new HashMap<>();
  327. request.put("users", users);
  328. Map<String, Object> response;
  329. try {
  330. response = webClientUtil.postWithToken(endpoint, request, Map.class, accessToken);
  331. } catch (Exception e) {
  332. throw new MyException(502, "调用Portal批量注册失败:" + e.getMessage());
  333. }
  334. if (response == null || !response.containsKey("code")) {
  335. throw new MyException(502, "Portal批量注册返回格式异常");
  336. }
  337. Number code = (Number) response.get("code");
  338. if (code == null || code.intValue() != 0) {
  339. throw new MyException(502, "Portal批量注册失败:" + String.valueOf(response.get("message")));
  340. }
  341. Object dataObj = response.get("data");
  342. if (!(dataObj instanceof Map)) {
  343. throw new MyException(502, "Portal批量注册返回数据为空");
  344. }
  345. Map<String, Object> data = (Map<String, Object>) dataObj;
  346. Object detailsObj = data.get("details");
  347. if (!(detailsObj instanceof List)) {
  348. throw new MyException(502, "Portal批量注册结果缺少details字段");
  349. }
  350. Map<Integer, Map<String, Object>> resultByRow = new HashMap<>();
  351. for (Object detailObj : (List<?>) detailsObj) {
  352. if (!(detailObj instanceof Map)) {
  353. continue;
  354. }
  355. Map<String, Object> detail = (Map<String, Object>) detailObj;
  356. Number rowIndex = (Number) detail.get("rowIndex");
  357. if (rowIndex != null) {
  358. resultByRow.put(rowIndex.intValue(), detail);
  359. }
  360. }
  361. return resultByRow;
  362. }
  363. private String validateRow(StudentImportRow row, Set<String> officialNumberSet, Set<String> phoneSet) {
  364. if (row.getStuName() == null || row.getStuName().isEmpty()
  365. || row.getOfficialNumber() == null || row.getOfficialNumber().isEmpty()
  366. || row.getPhone() == null || row.getPhone().isEmpty()) {
  367. return "必填字段缺失:学号/姓名/手机号不能为空";
  368. }
  369. if (!Pattern.matches("^\\d{11}$", row.getPhone())) {
  370. return "手机号格式错误,必须为11位数字";
  371. }
  372. if (officialNumberSet.contains(row.getOfficialNumber())) {
  373. return "导入文件中学号重复";
  374. }
  375. if (phoneSet.contains(row.getPhone())) {
  376. return "导入文件中手机号重复";
  377. }
  378. return null;
  379. }
  380. private String generateDefaultPassword(String officialNumber) {
  381. if (officialNumber == null || officialNumber.length() < 6) {
  382. throw new MyException(400, "学号长度不足6位,无法生成默认密码");
  383. }
  384. String suffix = officialNumber.substring(officialNumber.length() - 6);
  385. if (!Pattern.matches("^\\d{6}$", suffix)) {
  386. throw new MyException(400, "学号后6位必须是数字");
  387. }
  388. return "Stu" + suffix;
  389. }
  390. @Override
  391. @Transactional
  392. public String addStudentByOfficialNumber(Long classId, String officialNumber) {
  393. // 1. 检查班级是否存在
  394. Class cls = classMapper.selectById(classId);
  395. if (cls == null) {
  396. throw new MyException(404, "班级不存在");
  397. }
  398. // 2. 根据学号查询用户
  399. QueryWrapper<User> userWrapper = new QueryWrapper<>();
  400. userWrapper.eq("official_number", officialNumber);
  401. User user = userMapper.selectOne(userWrapper);
  402. if (user == null) {
  403. throw new MyException(404, "未找到学号为 " + officialNumber + " 的学生");
  404. }
  405. // 3. 检查是否已在班级中
  406. QueryWrapper<ClassStudent> existWrapper = new QueryWrapper<>();
  407. existWrapper.eq("class_id", classId)
  408. .eq("official_number", officialNumber);
  409. if (classStudentMapper.selectCount(existWrapper) > 0) {
  410. throw new MyException(400, "该学生已在班级中");
  411. }
  412. // 4. 创建学生记录
  413. ClassStudent student = new ClassStudent();
  414. student.setClassId(classId);
  415. student.setOfficialNumber(officialNumber);
  416. student.setStuName(user.getName()); // 从用户表获取姓名
  417. student.setState(ClassStudentState.NOT_JOINED); // 默认状态
  418. // 5. 插入记录
  419. int insertResult = classStudentMapper.insert(student);
  420. if (insertResult <= 0) {
  421. throw new MyException(500, "添加学生失败");
  422. }
  423. // 6. 更新班级人数
  424. classMapper.increaseStuNumber(classId, 1);
  425. return "成功添加学生: " + user.getName() + " (" + officialNumber + ")";
  426. }
  427. @Override
  428. @Transactional(rollbackFor = Exception.class)
  429. public String batchDeleteStudents(Long classId, List<String> officialNumbers) {
  430. // 1. 检查班级是否存在
  431. Class cls = classMapper.selectById(classId);
  432. if (cls == null) {
  433. throw new MyException(404, "班级不存在");
  434. }
  435. // 2. 验证输入
  436. if (officialNumbers == null || officialNumbers.isEmpty()) {
  437. throw new MyException(400, "学号列表不能为空");
  438. }
  439. // 3. 检查学生是否在班级中
  440. QueryWrapper<ClassStudent> wrapper = new QueryWrapper<>();
  441. wrapper.eq("class_id", classId)
  442. .in("official_number", officialNumbers);
  443. List<ClassStudent> students = classStudentMapper.selectList(wrapper);
  444. if (students.isEmpty()) {
  445. throw new MyException(404, "没有找到匹配的学生");
  446. }
  447. // 4. 获取实际存在的学号列表
  448. List<String> existingNumbers = students.stream()
  449. .map(ClassStudent::getOfficialNumber)
  450. .collect(Collectors.toList());
  451. // 5. 批量删除学生记录
  452. int deleteCount = classStudentMapper.batchDeleteByClassIdAndNumbers(classId, existingNumbers);
  453. if (deleteCount <= 0) {
  454. throw new MyException(500, "删除学生失败");
  455. }
  456. // 6. 更新班级人数
  457. int updateCount = classMapper.decreaseStuNumber(classId, deleteCount);
  458. if (updateCount <= 0) {
  459. throw new MyException(500, "更新班级人数失败");
  460. }
  461. // 7. 处理部分成功的情况
  462. List<String> notFoundNumbers = officialNumbers.stream()
  463. .filter(number -> !existingNumbers.contains(number))
  464. .collect(Collectors.toList());
  465. List<Long> studentIds = students.stream().map(student ->{
  466. User user = userMapper.selectByOfficialNumber(student.getOfficialNumber());
  467. return user.getId();
  468. }).toList();
  469. // 8. 删除课程,学生表中的记录
  470. Class aClass = classMapper.selectById(classId);
  471. courseStudentMapper.deleteByStudentIds(studentIds,aClass.getCourseId());
  472. String successMsg = "成功删除 " + deleteCount + " 名学生";
  473. if (!notFoundNumbers.isEmpty()) {
  474. String notFoundMsg = ",但以下学号未在班级中找到: " + String.join(", ", notFoundNumbers);
  475. successMsg += notFoundMsg;
  476. }
  477. return successMsg;
  478. }
  479. @Override
  480. public List<StudentInfoVO> getStudentsByClassId(Long classId) {
  481. return classStudentMapper.selectStudentsByClassId(classId);
  482. }
  483. @Override
  484. public Class getClassById(Long classId) {
  485. if (classId == null || classId <= 0) {
  486. log.warn("无效的 classId: {}", classId);
  487. throw new IllegalArgumentException("无效的班级ID");
  488. }
  489. Class courseClass = classMapper.selectById(classId);
  490. return courseClass;
  491. }
  492. @Override
  493. public Class getClassByStudentIdAndCourseId(Long studentId, Long courseId) {
  494. return classStudentMapper.findClassByStudentIdAndCourseId(studentId, courseId);
  495. }
  496. private List<StudentImportRow> parseExcelFile(MultipartFile file) throws IOException {
  497. List<StudentImportRow> rows = new ArrayList<>();
  498. try (Workbook workbook = new XSSFWorkbook(file.getInputStream())) {
  499. Sheet sheet = workbook.getSheetAt(0);
  500. Row headerRow = sheet.getRow(0);
  501. int nameCol = findColumnIndex(headerRow, "学生姓名", "姓名");
  502. int numberCol = findColumnIndex(headerRow, "学生学号", "学号");
  503. int phoneCol = findColumnIndex(headerRow, "手机号", "手机号码");
  504. if (nameCol == -1 || numberCol == -1 || phoneCol == -1) {
  505. throw new MyException(400, "文件缺少必要列:学生姓名 / 学生学号 / 手机号");
  506. }
  507. for (int i = 1; i <= sheet.getLastRowNum(); i++) {
  508. Row row = sheet.getRow(i);
  509. if (row == null) {
  510. continue;
  511. }
  512. StudentImportRow importRow = new StudentImportRow();
  513. importRow.setRowIndex(i + 1);
  514. importRow.setStuName(getCellStringValue(row.getCell(nameCol)));
  515. importRow.setOfficialNumber(getCellStringValue(row.getCell(numberCol)).replace(",", ""));
  516. importRow.setPhone(getCellStringValue(row.getCell(phoneCol)).replace(",", ""));
  517. rows.add(importRow);
  518. }
  519. }
  520. return rows;
  521. }
  522. private List<StudentImportRow> parseCsvFile(MultipartFile file)
  523. throws IOException, CsvValidationException {
  524. List<StudentImportRow> rows = new ArrayList<>();
  525. try (CSVReader csvReader = new CSVReader(new InputStreamReader(file.getInputStream()))) {
  526. String[] header = csvReader.readNext();
  527. if (header == null) {
  528. throw new MyException(400, "CSV文件为空");
  529. }
  530. int nameCol = findColumnIndex(header, "学生姓名", "姓名");
  531. int numberCol = findColumnIndex(header, "学生学号", "学号");
  532. int phoneCol = findColumnIndex(header, "手机号", "手机号码");
  533. if (nameCol == -1 || numberCol == -1 || phoneCol == -1) {
  534. throw new MyException(400, "文件缺少必要列:学生姓名 / 学生学号 / 手机号");
  535. }
  536. String[] nextRecord;
  537. int rowNum = 1;
  538. while ((nextRecord = csvReader.readNext()) != null) {
  539. rowNum++;
  540. int maxIndex = Math.max(Math.max(nameCol, numberCol), phoneCol);
  541. if (nextRecord.length < maxIndex + 1) {
  542. StudentImportRow importRow = new StudentImportRow();
  543. importRow.setRowIndex(rowNum);
  544. importRow.setStuName("");
  545. importRow.setOfficialNumber("");
  546. importRow.setPhone("");
  547. rows.add(importRow);
  548. continue;
  549. }
  550. StudentImportRow importRow = new StudentImportRow();
  551. importRow.setRowIndex(rowNum);
  552. importRow.setStuName(nextRecord[nameCol] == null ? "" : nextRecord[nameCol].trim());
  553. importRow.setOfficialNumber(nextRecord[numberCol] == null ? "" : nextRecord[numberCol].trim().replace(",", ""));
  554. importRow.setPhone(nextRecord[phoneCol] == null ? "" : nextRecord[phoneCol].trim().replace(",", ""));
  555. rows.add(importRow);
  556. }
  557. }
  558. return rows;
  559. }
  560. private int findColumnIndex(Row headerRow, String... columnNames) {
  561. if (headerRow == null) {
  562. return -1;
  563. }
  564. for (Cell cell : headerRow) {
  565. String value = getCellStringValue(cell);
  566. for (String columnName : columnNames) {
  567. if (value.equalsIgnoreCase(columnName)) {
  568. return cell.getColumnIndex();
  569. }
  570. }
  571. }
  572. return -1;
  573. }
  574. private int findColumnIndex(String[] header, String... columnNames) {
  575. for (int i = 0; i < header.length; i++) {
  576. String value = header[i] == null ? "" : header[i].trim();
  577. for (String columnName : columnNames) {
  578. if (value.equalsIgnoreCase(columnName)) {
  579. return i;
  580. }
  581. }
  582. }
  583. return -1;
  584. }
  585. private String getCellStringValue(Cell cell) {
  586. if (cell == null) {
  587. return "";
  588. }
  589. DataFormatter formatter = new DataFormatter();
  590. String cellValue = formatter.formatCellValue(cell).trim();
  591. if (cellValue.contains("E") && cell.getCellType() == CellType.NUMERIC) {
  592. BigDecimal bigDecimal = BigDecimal.valueOf(cell.getNumericCellValue());
  593. cellValue = bigDecimal.toPlainString();
  594. }
  595. return cellValue;
  596. }
  597. private static class StudentImportRow {
  598. private Integer rowIndex;
  599. private String officialNumber;
  600. private String stuName;
  601. private String phone;
  602. public Integer getRowIndex() {
  603. return rowIndex;
  604. }
  605. public void setRowIndex(Integer rowIndex) {
  606. this.rowIndex = rowIndex;
  607. }
  608. public String getOfficialNumber() {
  609. return officialNumber;
  610. }
  611. public void setOfficialNumber(String officialNumber) {
  612. this.officialNumber = officialNumber;
  613. }
  614. public String getStuName() {
  615. return stuName;
  616. }
  617. public void setStuName(String stuName) {
  618. this.stuName = stuName;
  619. }
  620. public String getPhone() {
  621. return phone;
  622. }
  623. public void setPhone(String phone) {
  624. this.phone = phone;
  625. }
  626. }
  627. }