Bladeren bron

Merge branch 'feat/delete_import' of FanYanPeng/EAI-Backend into refactor

LiZhi 6 maanden geleden
bovenliggende
commit
d3913e3ab7

+ 13 - 0
application.yaml

@@ -7,6 +7,16 @@ spring:
     username: root
     password: eai123456
     url: jdbc:mysql://8.130.28.19:3306/eai?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&allowPublicKeyRetrieval=true
+    hikari:
+      maximum-pool-size: 30
+      minimum-idle: 10
+      connection-timeout: 10000
+      validation-timeout: 3000
+      idle-timeout: 600000
+      # Keep maxLifetime shorter than MySQL wait_timeout to avoid stale pooled connections.
+      max-lifetime: 240000
+      keepalive-time: 120000
+      leak-detection-threshold: 30000
 #    url: jdbc:mysql://127.0.0.1:3306/eai?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&allowPublicKeyRetrieval=true
   data:
     mongodb:
@@ -45,6 +55,9 @@ jwt:
   secret: UenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRW
   issuer: eai
   expiration: 86400
+  portal-sync:
+    enabled: true
+    throttle-seconds: 60
 
 
 aliyun:

+ 40 - 2
src/main/java/com/njuzr/eaibackend/config/JWTAuthenticationFilter.java

@@ -12,6 +12,8 @@ import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.security.authentication.CredentialsExpiredException;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -21,6 +23,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
 
 import java.io.IOException;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 /**
  * @author: Leonezhurui
@@ -35,13 +38,24 @@ public class JWTAuthenticationFilter extends OncePerRequestFilter {
     private final JWTTokenUtil jwtTokenUtil;
     private final MyUserDetailService userDetailService;
     private final UserService userService;
+    private final RedisTemplate<String, Object> redisTemplate;
+
+    @Value("${jwt.portal-sync.enabled:true}")
+    private boolean portalSyncEnabled;
+
+    @Value("${jwt.portal-sync.throttle-seconds:60}")
+    private long portalSyncThrottleSeconds;
 
     private final MyAuthenticationEntryPoint authenticationEntryPoint = new MyAuthenticationEntryPoint();
 
-    public JWTAuthenticationFilter(JWTTokenUtil jwtTokenUtil, MyUserDetailService userDetailService, UserService userService) {
+    public JWTAuthenticationFilter(JWTTokenUtil jwtTokenUtil,
+                                   MyUserDetailService userDetailService,
+                                   UserService userService,
+                                   RedisTemplate<String, Object> redisTemplate) {
         this.jwtTokenUtil = jwtTokenUtil;
         this.userDetailService = userDetailService;
         this.userService = userService;
+        this.redisTemplate = redisTemplate;
     }
 
     // 原登录jwtTokenUtil.parseToken(token)获得的是学号,门户是手机号
@@ -60,7 +74,7 @@ public class JWTAuthenticationFilter extends OncePerRequestFilter {
             String phone = jwtTokenUtil.parseToken(token);
             Map<String, Object> userInfoMap = jwtTokenUtil.parseClaim(token).getBody().get("user_info", Map.class);
             // 不为空则认为是门户请求,此时同步用户信息
-            if(userInfoMap != null){
+            if (userInfoMap != null && shouldSyncUserInfo(userInfoMap, phone)) {
                 syncUserInfo(userInfoMap, phone);
             }
 
@@ -109,4 +123,28 @@ public class JWTAuthenticationFilter extends OncePerRequestFilter {
             userService.updateUserByPid(pid, update);
         }
     }
+
+    private boolean shouldSyncUserInfo(Map<String, Object> userInfoMap, String phone) {
+        if (!portalSyncEnabled || portalSyncThrottleSeconds <= 0) {
+            return true;
+        }
+
+        Object pid = userInfoMap.get("id");
+        String syncTarget = pid == null ? phone : pid.toString();
+        String throttleKey = "auth:portal-sync:" + syncTarget;
+
+        try {
+            Boolean shouldSync = redisTemplate.opsForValue().setIfAbsent(
+                    throttleKey,
+                    System.currentTimeMillis(),
+                    portalSyncThrottleSeconds,
+                    TimeUnit.SECONDS
+            );
+            return Boolean.TRUE.equals(shouldSync);
+        } catch (Exception e) {
+            // Redis is an optimization here; if unavailable we keep current auth behavior.
+            log.warn("shouldSyncUserInfo redis throttle failed, fallback to immediate sync. key={}", throttleKey, e);
+            return true;
+        }
+    }
 }

+ 84 - 64
src/main/java/com/njuzr/eaibackend/service/impl/ClassServiceImpl.java

@@ -220,12 +220,14 @@ public class ClassServiceImpl implements ClassService {
             return result;
         }
 
-        Map<Integer, Map<String, Object>> portalResultByRow = registerUsersInPortal(rows, accessToken);
         Set<String> fileOfficialNumbers = new HashSet<>();
         Set<String> filePhones = new HashSet<>();
 
+        List<StudentImportRow> rowsToRegister = new ArrayList<>();
+        Map<Integer, BatchStudentImportDetailVO> pendingRegisterDetails = new HashMap<>();
         List<User> usersToInsert = new ArrayList<>();
         List<ClassStudent> studentsToInsert = new ArrayList<>();
+        Set<Long> enrollmentStudentIds = new HashSet<>();
 
         for (StudentImportRow row : rows) {
             BatchStudentImportDetailVO detail = new BatchStudentImportDetailVO();
@@ -233,31 +235,43 @@ public class ClassServiceImpl implements ClassService {
             detail.setOfficialNumber(row.getOfficialNumber());
             detail.setStuName(row.getStuName());
             detail.setPhone(row.getPhone());
+            result.getDetails().add(detail);
 
             String validationError = validateRow(row, fileOfficialNumbers, filePhones);
             if (validationError != null) {
                 detail.setStatus("FAILED");
                 detail.setMessage(validationError);
-                result.getDetails().add(detail);
                 result.setFailedCount(result.getFailedCount() + 1);
                 continue;
             }
 
-            Map<String, Object> portalRow = portalResultByRow.get(row.getRowIndex());
-            if (portalRow == null || !"SUCCESS".equals(String.valueOf(portalRow.get("status")))) {
+            User existsByNumber = userMapper.selectByOfficialNumber(row.getOfficialNumber());
+
+            QueryWrapper<ClassStudent> classStudentWrapper = new QueryWrapper<>();
+            classStudentWrapper.eq("class_id", classId)
+                    .eq("official_number", row.getOfficialNumber());
+            if (classStudentMapper.selectCount(classStudentWrapper) > 0) {
                 detail.setStatus("SKIPPED");
-                detail.setMessage(portalRow == null ? "Portal未返回该行处理结果" : String.valueOf(portalRow.get("message")));
-                result.getDetails().add(detail);
+                detail.setMessage("学生已在班级中,已跳过");
                 result.setSkippedCount(result.getSkippedCount() + 1);
                 continue;
             }
 
-            User existsByNumber = userMapper.selectByOfficialNumber(row.getOfficialNumber());
             if (existsByNumber != null) {
-                detail.setStatus("SKIPPED");
-                detail.setMessage("EAI中学号已存在,已跳过");
-                result.getDetails().add(detail);
-                result.setSkippedCount(result.getSkippedCount() + 1);
+                ClassStudent student = new ClassStudent();
+                student.setClassId(classId);
+                student.setOfficialNumber(row.getOfficialNumber());
+                student.setStuName(row.getStuName());
+                student.setState(ClassStudentState.NOT_JOINED);
+                studentsToInsert.add(student);
+
+                enrollmentStudentIds.add(existsByNumber.getId());
+
+                detail.setStatus("SUCCESS");
+                detail.setMessage("学生已存在,已关联班级和课程");
+                result.setSuccessCount(result.getSuccessCount() + 1);
+                fileOfficialNumbers.add(row.getOfficialNumber());
+                filePhones.add(row.getPhone());
                 continue;
             }
 
@@ -265,73 +279,79 @@ public class ClassServiceImpl implements ClassService {
             if (existsByPhone != null) {
                 detail.setStatus("SKIPPED");
                 detail.setMessage("EAI中手机号已存在,已跳过");
-                result.getDetails().add(detail);
-                result.setSkippedCount(result.getSkippedCount() + 1);
-                continue;
-            }
-
-            QueryWrapper<ClassStudent> classStudentWrapper = new QueryWrapper<>();
-            classStudentWrapper.eq("class_id", classId)
-                    .eq("official_number", row.getOfficialNumber());
-            if (classStudentMapper.selectCount(classStudentWrapper) > 0) {
-                detail.setStatus("SKIPPED");
-                detail.setMessage("学生已在班级中,已跳过");
-                result.getDetails().add(detail);
                 result.setSkippedCount(result.getSkippedCount() + 1);
                 continue;
             }
 
-            User user = new User();
-            user.setName(row.getStuName());
-            user.setOfficialNumber(row.getOfficialNumber());
-            user.setPhone(row.getPhone());
-            user.setRole(Role.STUDENT);
-            user.setCreateTime(new Date());
-            Object portalUserId = portalRow.get("userId");
-            if (portalUserId instanceof Number) {
-                user.setPid(String.valueOf(((Number) portalUserId).longValue()));
-            }
-            user.setPassword(passwordEncoder.encode(generateDefaultPassword(row.getOfficialNumber())));
-            usersToInsert.add(user);
-
-            ClassStudent student = new ClassStudent();
-            student.setClassId(classId);
-            student.setOfficialNumber(row.getOfficialNumber());
-            student.setStuName(row.getStuName());
-            student.setState(ClassStudentState.NOT_JOINED);
-            studentsToInsert.add(student);
-
-            detail.setStatus("SUCCESS");
-            detail.setMessage("导入成功");
-            result.getDetails().add(detail);
-            result.setSuccessCount(result.getSuccessCount() + 1);
+            rowsToRegister.add(row);
+            pendingRegisterDetails.put(row.getRowIndex(), detail);
             fileOfficialNumbers.add(row.getOfficialNumber());
             filePhones.add(row.getPhone());
         }
 
+        if (!rowsToRegister.isEmpty()) {
+            Map<Integer, Map<String, Object>> portalResultByRow = registerUsersInPortal(rowsToRegister, accessToken);
+            for (StudentImportRow row : rowsToRegister) {
+                BatchStudentImportDetailVO detail = pendingRegisterDetails.get(row.getRowIndex());
+                Map<String, Object> portalRow = portalResultByRow.get(row.getRowIndex());
+                if (portalRow == null || !"SUCCESS".equals(String.valueOf(portalRow.get("status")))) {
+                    detail.setStatus("SKIPPED");
+                    detail.setMessage(portalRow == null ? "Portal未返回该行处理结果" : String.valueOf(portalRow.get("message")));
+                    result.setSkippedCount(result.getSkippedCount() + 1);
+                    continue;
+                }
+
+                User user = new User();
+                user.setName(row.getStuName());
+                user.setOfficialNumber(row.getOfficialNumber());
+                user.setPhone(row.getPhone());
+                user.setRole(Role.STUDENT);
+                user.setCreateTime(new Date());
+                Object portalUserId = portalRow.get("userId");
+                if (portalUserId instanceof Number) {
+                    user.setPid(String.valueOf(((Number) portalUserId).longValue()));
+                }
+                user.setPassword(passwordEncoder.encode(generateDefaultPassword(row.getOfficialNumber())));
+                usersToInsert.add(user);
+
+                ClassStudent student = new ClassStudent();
+                student.setClassId(classId);
+                student.setOfficialNumber(row.getOfficialNumber());
+                student.setStuName(row.getStuName());
+                student.setState(ClassStudentState.NOT_JOINED);
+                studentsToInsert.add(student);
+
+                detail.setStatus("SUCCESS");
+                detail.setMessage("导入成功");
+                result.setSuccessCount(result.getSuccessCount() + 1);
+            }
+        }
+
         if (!usersToInsert.isEmpty()) {
             userMapper.batchInsert(usersToInsert);
-            classStudentMapper.batchInsert(studentsToInsert);
-            classMapper.increaseStuNumber(classId, studentsToInsert.size());
-
-            List<String> importedOfficialNumbers = studentsToInsert.stream()
-                    .map(ClassStudent::getOfficialNumber)
+            List<String> importedOfficialNumbers = usersToInsert.stream()
+                    .map(User::getOfficialNumber)
                     .collect(Collectors.toList());
             List<User> importedUsers = userMapper.selectUserIdByOfficialNumbers(importedOfficialNumbers);
-            List<Enrollment> enrollments = new ArrayList<>();
             for (User importedUser : importedUsers) {
+                enrollmentStudentIds.add(importedUser.getId());
+            }
+        }
+
+        if (!studentsToInsert.isEmpty()) {
+            classStudentMapper.batchInsert(studentsToInsert);
+            classMapper.increaseStuNumber(classId, studentsToInsert.size());
+        }
+
+        for (Long studentId : enrollmentStudentIds) {
+            QueryWrapper<Enrollment> enrollmentWrapper = new QueryWrapper<>();
+            enrollmentWrapper.eq("course_id", cls.getCourseId())
+                    .eq("student_id", studentId);
+            if (!courseStudentMapper.exists(enrollmentWrapper)) {
                 Enrollment enrollment = new Enrollment();
                 enrollment.setCourseId(cls.getCourseId());
-                enrollment.setStudentId(importedUser.getId());
-                enrollments.add(enrollment);
-            }
-            for (Enrollment enrollment : enrollments) {
-                QueryWrapper<Enrollment> enrollmentWrapper = new QueryWrapper<>();
-                enrollmentWrapper.eq("course_id", enrollment.getCourseId())
-                        .eq("student_id", enrollment.getStudentId());
-                if (!courseStudentMapper.exists(enrollmentWrapper)) {
-                    courseStudentMapper.insert(enrollment);
-                }
+                enrollment.setStudentId(studentId);
+                courseStudentMapper.insert(enrollment);
             }
         }
 
@@ -359,7 +379,7 @@ public class ClassServiceImpl implements ClassService {
 
         Map<String, Object> response;
         try {
-            response = webClientUtil.post(endpoint, request, Map.class);
+            response = webClientUtil.postWithToken(endpoint, request, Map.class, accessToken);
         } catch (Exception e) {
             throw new MyException(502, "调用Portal批量注册失败:" + e.getMessage());
         }

+ 13 - 0
src/main/resources/application-dev.yaml

@@ -7,6 +7,16 @@ spring:
     username: root
     password: eai123456
     url: jdbc:mysql://139.196.252.184:3306/eai?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&allowPublicKeyRetrieval=true
+    hikari:
+      maximum-pool-size: 30
+      minimum-idle: 10
+      connection-timeout: 10000
+      validation-timeout: 3000
+      idle-timeout: 600000
+      # Keep maxLifetime shorter than MySQL wait_timeout to avoid stale pooled connections.
+      max-lifetime: 240000
+      keepalive-time: 120000
+      leak-detection-threshold: 30000
   data:
     mongodb:
       uri: mongodb://admin:eai123456@139.196.252.184:27017/eai?authSource=admin
@@ -32,6 +42,9 @@ jwt:
   secret: UenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRW
   issuer: eai
   expiration: 86400
+  portal-sync:
+    enabled: true
+    throttle-seconds: 60
 
 
 aliyun:

+ 13 - 0
src/main/resources/application-prod.yaml

@@ -7,6 +7,16 @@ spring:
     username: root
     password: eai123456
     url: jdbc:mysql://139.196.252.184:3306/eai?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&allowPublicKeyRetrieval=true
+    hikari:
+      maximum-pool-size: 30
+      minimum-idle: 10
+      connection-timeout: 10000
+      validation-timeout: 3000
+      idle-timeout: 600000
+      # Keep maxLifetime shorter than MySQL wait_timeout to avoid stale pooled connections.
+      max-lifetime: 240000
+      keepalive-time: 120000
+      leak-detection-threshold: 30000
   data:
     mongodb:
       uri: mongodb://admin:eai123456@139.196.252.184:27017/eai?authSource=admin
@@ -34,6 +44,9 @@ jwt:
   secret: UenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRW
   issuer: eai
   expiration: 86400
+  portal-sync:
+    enabled: true
+    throttle-seconds: 60
 
 
 aliyun: