lzzz 4 месяцев назад
Родитель
Сommit
f5aaa22ad2

+ 38 - 6
src/main/java/com/njuzr/eaibackend/config/JWTAuthenticationFilter.java

@@ -95,12 +95,12 @@ public class JWTAuthenticationFilter extends OncePerRequestFilter {
     }
 
     private void syncUserInfo(Map<String, Object> userInfoMap, String phone) {
-        log.info("syncUserInfo user info in token:{}", userInfoMap.toString());
-        Integer pid = Integer.valueOf(userInfoMap.get("id").toString());
-        String name = userInfoMap.get("name").toString();
-        String email = userInfoMap.get("email").toString();
-        String username = userInfoMap.get("username").toString();
-        Role role = Role.valueOf(userInfoMap.get("role").toString());
+        log.info("syncUserInfo user info in token:{}", userInfoMap);
+        Integer pid = parseRequiredInt(userInfoMap, "id");
+        String name = parseRequiredString(userInfoMap, "name");
+        String email = parseOptionalString(userInfoMap, "email");
+        String username = parseRequiredString(userInfoMap, "username");
+        Role role = Role.valueOf(parseRequiredString(userInfoMap, "role"));
         // 如果EAI数据库中没有该用户,则添加该用户
         if (!userService.userExists(pid)) {
             log.info("syncUserInfo user don't exist, add: {}", phone);
@@ -124,6 +124,38 @@ public class JWTAuthenticationFilter extends OncePerRequestFilter {
         }
     }
 
+    private String parseRequiredString(Map<String, Object> map, String key) {
+        Object value = map.get(key);
+        if (value == null) {
+            throw new IllegalArgumentException("portal token missing field: " + key);
+        }
+        String text = value.toString().trim();
+        if (text.isEmpty()) {
+            throw new IllegalArgumentException("portal token empty field: " + key);
+        }
+        return text;
+    }
+
+    private String parseOptionalString(Map<String, Object> map, String key) {
+        Object value = map.get(key);
+        if (value == null) {
+            return null;
+        }
+        String text = value.toString().trim();
+        return text.isEmpty() ? null : text;
+    }
+
+    private Integer parseRequiredInt(Map<String, Object> map, String key) {
+        Object value = map.get(key);
+        if (value == null) {
+            throw new IllegalArgumentException("portal token missing field: " + key);
+        }
+        if (value instanceof Number) {
+            return ((Number) value).intValue();
+        }
+        return Integer.valueOf(value.toString());
+    }
+
     private boolean shouldSyncUserInfo(Map<String, Object> userInfoMap, String phone) {
         if (!portalSyncEnabled || portalSyncThrottleSeconds <= 0) {
             return true;

+ 33 - 0
src/main/java/com/njuzr/eaibackend/controller/ClassController.java

@@ -1,6 +1,7 @@
 package com.njuzr.eaibackend.controller;
 
 import com.njuzr.eaibackend.dto.ClassDTO;
+import com.njuzr.eaibackend.dto.SingleStudentImportDTO;
 import com.njuzr.eaibackend.exception.MyException;
 import com.njuzr.eaibackend.po.Class;
 import com.njuzr.eaibackend.service.ClassService;
@@ -10,6 +11,7 @@ import com.njuzr.eaibackend.vo.StudentInfoVO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.util.StringUtils;
+import jakarta.validation.Valid;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
@@ -106,6 +108,37 @@ public class ClassController {
         }
     }
 
+    /**
+     * 单独导入一个学生到班级并完成注册
+     * @param classId 班级ID
+     * @param dto 学生信息(学号/姓名/手机号)
+     * @return 导入结果(与批量导入结构一致)
+     */
+    @PostMapping("/{classId}/students/import")
+    @PreAuthorize("hasRole('ADMIN') or hasRole('TEACHER')")
+    public MyResponse importSingleStudent(
+            @PathVariable Long classId,
+            @Valid @RequestBody SingleStudentImportDTO dto,
+            @RequestHeader(value = "Authorization", required = false) String authorization) {
+
+        try {
+            if (!StringUtils.hasText(authorization)) {
+                return MyResponse.error(401, "缺少Authorization令牌");
+            }
+            String token = authorization.startsWith("Bearer ") ? authorization.substring(7).trim() : authorization.trim();
+            if (!StringUtils.hasText(token)) {
+                return MyResponse.error(401, "无效的Authorization令牌");
+            }
+            BatchStudentImportResultVO result = classService.importSingleStudent(classId, dto, token);
+            return MyResponse.success(result);
+        } catch (MyException e) {
+            return MyResponse.error(e.getErrCode(), e.getMessage());
+        } catch (Exception e) {
+            log.error("单独导入学生失败", e);
+            return MyResponse.error(500, "服务器内部错误");
+        }
+    }
+
     /**
      * 通过学号添加学生到班级
      * @param classId 班级ID

+ 4 - 0
src/main/java/com/njuzr/eaibackend/service/ClassService.java

@@ -1,6 +1,7 @@
 package com.njuzr.eaibackend.service;
 
 import com.njuzr.eaibackend.dto.ClassDTO;
+import com.njuzr.eaibackend.dto.SingleStudentImportDTO;
 import com.njuzr.eaibackend.po.Class;
 import com.njuzr.eaibackend.vo.BatchStudentImportResultVO;
 import com.njuzr.eaibackend.vo.StudentInfoVO;
@@ -24,6 +25,9 @@ public interface ClassService {
     String deleteClassWithCheck(Long classId);
 
     BatchStudentImportResultVO batchAddStudents(Long classId, MultipartFile file, String accessToken);
+
+    BatchStudentImportResultVO importSingleStudent(Long classId, SingleStudentImportDTO dto, String accessToken);
+
     /**
      * 通过学号添加学生到班级
      * @param classId 班级ID

+ 56 - 7
src/main/java/com/njuzr/eaibackend/service/impl/AuthenticationServiceImpl.java

@@ -106,14 +106,17 @@ public class AuthenticationServiceImpl implements AuthenticationService {
     public MyResponse loginByPortal(String token) {
         // 从门户返回的jwt中解析用户信息
         Map<String, Object> userInfoMap = jwtTokenUtil.parseClaim(token).getBody().get("user_info", Map.class);
-        log.info("解析出的用户信息:{}", userInfoMap.toString());
+        if (userInfoMap == null) {
+            throw new MyException(HttpStatus.BAD_REQUEST.value(), "门户token缺少user_info");
+        }
+        log.info("解析出的用户信息:{}", userInfoMap);
         //pid:门户用户id
-        Integer pid = Integer.valueOf(userInfoMap.get("id").toString());
-        String username = userInfoMap.get("username").toString();
-        String name = userInfoMap.get("name").toString();
-        String email = userInfoMap.get("email").toString();
-        String phone = userInfoMap.get("phone").toString();
-        Role role = Role.valueOf(userInfoMap.get("role").toString());
+        Integer pid = parseRequiredInt(userInfoMap, "id");
+        String username = parseRequiredString(userInfoMap, "username");
+        String name = parseRequiredString(userInfoMap, "name");
+        String email = parseOptionalString(userInfoMap, "email");
+        String phone = parseRequiredString(userInfoMap, "phone");
+        Role role = parseRequiredRole(userInfoMap, "role");
         // 如果EAI数据库中没有该用户,则添加该用户
         if (!userService.userExists(pid)) {
             log.info("用户不存在,添加用户{}", phone);
@@ -130,6 +133,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
             // 否则更新用户信息
             UserUpdateDTO update = new UserUpdateDTO();
             update.setContentEmail(email);
+            update.setPhone(phone);
             update.setOfficialNumber(username);
             update.setRole(role);
             System.out.println(role);
@@ -140,5 +144,50 @@ public class AuthenticationServiceImpl implements AuthenticationService {
         return MyResponse.success(userLoginVO);
     }
 
+    private String parseRequiredString(Map<String, Object> map, String key) {
+        Object value = map.get(key);
+        if (value == null) {
+            throw new MyException(HttpStatus.BAD_REQUEST.value(), "门户token缺少字段: " + key);
+        }
+        String text = value.toString().trim();
+        if (text.isEmpty()) {
+            throw new MyException(HttpStatus.BAD_REQUEST.value(), "门户token字段为空: " + key);
+        }
+        return text;
+    }
+
+    private String parseOptionalString(Map<String, Object> map, String key) {
+        Object value = map.get(key);
+        if (value == null) {
+            return null;
+        }
+        String text = value.toString().trim();
+        return text.isEmpty() ? null : text;
+    }
+
+    private Integer parseRequiredInt(Map<String, Object> map, String key) {
+        Object value = map.get(key);
+        if (value == null) {
+            throw new MyException(HttpStatus.BAD_REQUEST.value(), "门户token缺少字段: " + key);
+        }
+        try {
+            if (value instanceof Number) {
+                return ((Number) value).intValue();
+            }
+            return Integer.valueOf(value.toString());
+        } catch (NumberFormatException e) {
+            throw new MyException(HttpStatus.BAD_REQUEST.value(), "门户token字段格式错误: " + key);
+        }
+    }
+
+    private Role parseRequiredRole(Map<String, Object> map, String key) {
+        String role = parseRequiredString(map, key);
+        try {
+            return Role.valueOf(role);
+        } catch (IllegalArgumentException e) {
+            throw new MyException(HttpStatus.BAD_REQUEST.value(), "门户token角色非法: " + role);
+        }
+    }
+
 
 }

+ 25 - 7
src/main/java/com/njuzr/eaibackend/service/impl/ClassServiceImpl.java

@@ -2,6 +2,7 @@ package com.njuzr.eaibackend.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.njuzr.eaibackend.dto.ClassDTO;
+import com.njuzr.eaibackend.dto.SingleStudentImportDTO;
 import com.njuzr.eaibackend.enums.ClassStudentState;
 import com.njuzr.eaibackend.enums.Role;
 import com.njuzr.eaibackend.exception.MyException;
@@ -53,7 +54,7 @@ public class ClassServiceImpl implements ClassService {
     private final CourseStudentMapper courseStudentMapper;
     private final WebClientUtil webClientUtil;
 
-    @Value("${portal.base-url:https://p-nju.seec.seecoder.cn}")
+    @Value("${portal.base-url:http://localhost:8080}")
     private String portalBaseUrl;
 
     @Value("${portal.batch-register-path:/api/user/register/batch/internal}")
@@ -187,12 +188,6 @@ public class ClassServiceImpl implements ClassService {
     @Override
     @Transactional
     public BatchStudentImportResultVO batchAddStudents(Long classId, MultipartFile file, String accessToken) {
-        // 检查班级是否存在
-        Class cls = classMapper.selectById(classId);
-        if (cls == null) {
-            throw new MyException(404, "班级不存在");
-        }
-
         // 解析文件
         List<StudentImportRow> rows;
         String fileName = file.getOriginalFilename();
@@ -210,6 +205,29 @@ public class ClassServiceImpl implements ClassService {
             throw new MyException(500, "解析学生文件失败: " + e.getMessage());
         }
 
+        return importStudents(classId, rows, accessToken);
+    }
+
+    @Override
+    @Transactional
+    public BatchStudentImportResultVO importSingleStudent(Long classId, SingleStudentImportDTO dto, String accessToken) {
+        List<StudentImportRow> rows = new ArrayList<>();
+        StudentImportRow row = new StudentImportRow();
+        row.setRowIndex(1);
+        row.setOfficialNumber(dto.getOfficialNumber() == null ? "" : dto.getOfficialNumber().trim());
+        row.setStuName(dto.getStuName() == null ? "" : dto.getStuName().trim());
+        row.setPhone(dto.getPhone() == null ? "" : dto.getPhone().trim());
+        rows.add(row);
+        return importStudents(classId, rows, accessToken);
+    }
+
+    private BatchStudentImportResultVO importStudents(Long classId, List<StudentImportRow> rows, String accessToken) {
+        // 检查班级是否存在
+        Class cls = classMapper.selectById(classId);
+        if (cls == null) {
+            throw new MyException(404, "班级不存在");
+        }
+
         BatchStudentImportResultVO result = new BatchStudentImportResultVO();
         result.setTotal(rows.size());
         result.setSuccessCount(0);