基于Spring Boot的留学生教务系统设计与实现:多语言、权限与高并发实战
发布时间:2026/9/5 12:19:24 锦皓数字建站

简介本资源是一套面向高等院校信息化建设的留学生教务管理系统完整开发工程专为Java中高级开发者及高校教务信息化项目实践者设计解决留学生学籍、课程、成绩与教学资源协同管理的实际业务痛点。压缩包共395个文件总大小1.12MB涵盖315个Java核心业务源码实现学生信息管理、课表编排、成绩录入等全流程逻辑、47个XML配置文件支撑Spring框架集成与MyBatis映射、10个YAML配置适配多环境部署、5个Dockerfile含多版本容器化构建方案、Jenkinsfile及备份脚本支持CI/CD流程以及pom.xml、mvnw、SQL建表脚本等标准化工程构件。已有284人学习下载可直接导入IDE运行调试快速掌握企业级教务系统分层架构设计、配置驱动开发与DevOps落地实践。1. 项目概述一个面向留学生的教务管理核心最近在整理过往的项目资料翻到了一个几年前为某高校国际教育学院做的“留学生教务管理系统”的Java源码。这个项目虽然不算特别前沿但麻雀虽小五脏俱全从需求分析、技术选型到编码实现、部署上线完整地走了一遍。今天拿出来聊聊不是要展示多么高深的技术而是想分享一下在特定业务场景留学生管理下一个教务系统从设计到落地有哪些值得注意的“坑”和“技巧”。如果你正在学习Java Web开发或者对教育类管理系统的设计感兴趣这篇内容或许能给你一些直接的参考。这个系统的核心目标很明确为高校的留学生办公室International Student Office提供一个一体化的在线管理平台。它需要处理从学生入学申请、课程注册、成绩管理到签证状态跟踪、住宿安排等一系列复杂且具有国际特色的业务流程。与普通的教务系统相比留学生管理涉及更多跨文化、多语言、合规性如移民局规定的要求这直接影响了我们的数据库设计、业务逻辑和用户交互。2. 核心需求与业务场景拆解为什么留学生教务系统需要单独设计直接套用国内学生的系统不行吗这是我接手项目时第一个思考的问题。经过和院方老师多次沟通我梳理出了几个核心差异点这些点直接决定了我们技术方案的设计方向。2.1 多语言与国际化支持这是最直观的需求。系统界面需要支持中英文切换甚至未来可能扩展其他语言。更深层次的是数据层面的国际化学生姓名需要支持包含空格、连字符在内的多种格式例如 “Jean-Pierre”地址信息要兼容全球各国的不同格式日期时间必须统一处理为UTC或带时区信息并在前端根据用户所在地正确显示。注意很多初学者会直接用String类型存储姓名但在排序、检索时可能会出现问题。我们采用了将姓名拆分为family_name、given_name和middle_name字段的方式并统一存储为Unicode编码确保任何语言的字符都能正确保存和显示。2.2 复杂的学籍与签证状态联动留学生的合法学习资格与其签证状态紧密绑定。系统中必须有一个状态机来管理学生的生命周期从“已录取”、“待注册”、“在读”到“休学”、“转学”、“毕业”直至“签证过期”、“非法滞留”等。任何一个学业状态的变化如挂科过多都可能触发对签证状态的审查预警。这要求我们的业务逻辑层有极强的规则引擎和事件驱动能力。2.3 灵活的课程与学分体系不同国家、不同项目的学分要求、课程体系差异巨大。系统需要支持自定义的毕业要求模板比如“必须修满24学分核心课 6学分选修课且GPA不低于3.0”。同时课程可能有先修课程Prerequisite要求这在排课和选课时需要进行复杂的校验。2.4 权限与数据隔离用户角色众多留学生、授课教师、学术导师、院系管理员、国际处工作人员、宿舍管理员等。不同角色看到的数据视图和操作权限天差地别。例如学生只能看到自己的成绩和课表教师可以看到所教班级所有学生的信息而宿舍管理员只能看到住宿相关的字段。这就要求权限系统设计必须精细到数据行级Row-Level Security。3. 技术栈选型与架构设计基于以上业务特点我们选择了当时现在看依然主流的经典Java EE技术栈并做了一些针对性强化。后端核心框架Spring Boot 2.x。选择它是因为能快速搭建、约定优于配置内嵌Tomcat也简化了部署。它强大的依赖注入和AOP支持非常适合构建模块化、易于测试的业务系统。ORMMyBatis-Plus。没有用全自动化的Hibernate而是选了MyBatis-Plus。原因在于留学生业务中有大量复杂、动态的查询如多条件组合筛选学生直接编写和优化SQL更直观、可控。MyBatis-Plus在提供单表CRUD便捷性的同时保留了原生SQL的灵活性。安全Spring Security JWT。用于实现认证和授权。JWTJSON Web Token非常适合前后端分离的无状态API将用户角色、权限信息直接编码在Token中减轻服务器会话存储压力。前端采用了Vue.js Element UI。前后端完全分离通过RESTful API交互。Element UI的组件库能快速搭建出符合管理后台气质的中英文界面。数据库MySQL 8.0。考虑到事务一致性和复杂查询的性能关系型数据库仍是首选。我们充分利用了MySQL的窗口函数来处理成绩排名、GPA计算等分析性查询。架构模式经典的三层架构Controller-Service-Dao基础上引入了DDD领域驱动设计的一些思想。我们将“学生”、“课程”、“注册记录”等核心概念建模为聚合根Aggregate Root确保业务逻辑内聚在对应的领域服务如StudentRegistrationService中避免了贫血模型和事务脚本的弊端。4. 数据库设计与核心表结构解析数据库设计是系统的基石尤其对于业务规则复杂的系统。这里挑几个核心表讲讲设计思路。4.1 学生信息表 (t_student)这张表是核心中的核心。除了基础的个人信息我们重点设计了以下几个字段CREATE TABLE t_student ( id bigint NOT NULL COMMENT 主键, student_number varchar(32) NOT NULL COMMENT 学号全球唯一, family_name varchar(100) NOT NULL COMMENT 姓, given_name varchar(100) NOT NULL COMMENT 名, preferred_name varchar(200) DEFAULT NULL COMMENT 常用名昵称, nationality_code char(3) NOT NULL COMMENT 国籍代码(ISO 3166-1 alpha-3), passport_number varchar(50) NOT NULL COMMENT 护照号, visa_type varchar(20) DEFAULT NULL COMMENT 签证类型, visa_expiry_date date DEFAULT NULL COMMENT 签证到期日, program_id bigint DEFAULT NULL COMMENT 所属项目ID, enrollment_status varchar(30) NOT NULL DEFAULT ADMITTED COMMENT 学籍状态ADMITTED/REGISTERED/IN_PROGRESS..., create_time datetime NOT NULL, update_time datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_student_number (student_number), UNIQUE KEY uk_passport (passport_number, nationality_code), KEY idx_status_program (enrollment_status, program_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci COMMENT留学生信息表;设计要点姓名拆分如前所述family_name和given_name分开存储便于按姓氏排序和尊重不同文化的姓名习惯。preferred_name字段很实用方便老师和同学称呼。标准化代码nationality_code使用ISO标准的三位国家代码避免直接存储国家名称带来的歧义和冗余。唯一性约束除了主键和学号我们还对(passport_number, nationality_code)建立了唯一约束因为同一本护照对应唯一一个人这是一个重要的业务唯一键。字符集utf8mb4_unicode_ci字符集确保支持所有语言的字符包括emoji虽然业务中用不到但以防万一。索引策略除了主键索引uk_student_number用于快速登录和查找idx_status_program则用于高频的“按项目和状态筛选学生”查询。4.2 课程注册表 (t_course_registration)这张表记录了学生选课的核心事实是业务逻辑最复杂的地方之一。CREATE TABLE t_course_registration ( id bigint NOT NULL, student_id bigint NOT NULL, course_offering_id bigint NOT NULL COMMENT 课程开设ID关联具体学期、班级, registration_status varchar(20) NOT NULL DEFAULT PENDING COMMENT 状态PENDING/APPROVED/DROPPED/WITHDRAWN, registration_time datetime NOT NULL COMMENT 选课时间, approved_by bigint DEFAULT NULL COMMENT 批准人教师或管理员, approved_time datetime DEFAULT NULL, final_grade varchar(5) DEFAULT NULL COMMENT 最终成绩如A B 87.5, grade_point decimal(3,2) DEFAULT NULL COMMENT 绩点如4.0 3.7, is_audit tinyint(1) DEFAULT 0 COMMENT 是否为旁听, PRIMARY KEY (id), UNIQUE KEY uk_student_course (student_id, course_offering_id), -- 防止重复选课 KEY idx_student_status (student_id, registration_status), KEY idx_course (course_offering_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT课程注册记录表;设计要点与业务逻辑状态驱动registration_status字段是整个选课业务流程的引擎。从PENDING待审核到APPROVED已批准可能因为名额已满、先修课不满足等原因变为REJECTED。开课后学生可以DROP退课或WITHDRAW中期退出两者对成绩单的影响不同。所有状态变更都需要记录操作人和时间并可能触发消息通知。唯一约束uk_student_course确保了同一学生在同一教学班只能有一条有效注册记录这是业务硬性规则。成绩存储final_grade存储原始成绩如“A”、“87.5”grade_point存储换算后的标准绩点用于计算GPA。这里有个坑不同学校、不同课程的绩点换算规则可能不同我们将其抽象为一个可配置的“成绩换算规则表”由管理员维护在录入成绩时自动触发换算服务。旁听标识is_audit字段标记旁听生旁听生的成绩通常不计入GPA。4.3 业务规则与配置表为了系统灵活性我们将大量业务规则外置到配置表中。例如t_graduation_requirement定义每个专业项目的毕业学分、必修课列表等。t_grade_conversion_rule定义成绩等级与绩点的换算关系。t_visa_status_rule定义学业状态如GPA低于2.0与签证状态预警的映射关系。这样做的好处是当学校政策变化时管理员可以通过后台界面修改配置而无需开发人员修改代码和重新部署。5. 核心业务模块实现详解有了扎实的数据模型我们来看看几个关键业务模块在Java中是如何实现的。5.1 学生选课服务事务与并发控制选课是系统的高并发场景尤其是热门课程开放选课时。核心服务CourseRegistrationService必须处理好两件事业务规则校验和资源竞争课程名额。Service Transactional(rollbackFor Exception.class) Slf4j public class CourseRegistrationServiceImpl implements CourseRegistrationService { Autowired private CourseOfferingMapper courseOfferingMapper; Autowired private CourseRegistrationMapper registrationMapper; Autowired private PrerequisiteChecker prerequisiteChecker; Autowired private NotificationService notificationService; Override public RegistrationResult registerCourse(Long studentId, Long courseOfferingId) { // 1. 基础校验学生状态、课程状态是否可选 Student student validateStudentEligibility(studentId); CourseOffering offering validateCourseOffering(courseOfferingId); // 2. 业务规则校验先修课、时间冲突、已修学分上限等 prerequisiteChecker.check(studentId, offering); // 3. 核心扣减课程名额使用悲观锁或乐观锁 int updateCount courseOfferingMapper.decreaseAvailableSeatsWithLock(courseOfferingId); if (updateCount 0) { throw new BusinessException(课程名额已满); } // 4. 创建注册记录 CourseRegistration registration new CourseRegistration(); registration.setStudentId(studentId); registration.setCourseOfferingId(courseOfferingId); registration.setRegistrationStatus(RegistrationStatus.PENDING); registration.setRegistrationTime(LocalDateTime.now()); registrationMapper.insert(registration); // 5. 发送待审核通知异步 notificationService.sendRegistrationPendingNotification(student, offering); log.info(学生[{}]成功提交课程[{}]选课申请, studentId, courseOfferingId); return RegistrationResult.success(registration.getId()); } // 辅助校验方法省略... }关键技术点事务管理整个registerCourse方法被Transactional注解包裹确保“校验-扣名额-创建记录-发通知”要么全部成功要么全部回滚。特别是名额扣减必须在事务内完成。并发控制decreaseAvailableSeatsWithLock方法内部使用了SELECT ... FOR UPDATE悲观锁或者使用基于版本的乐观锁UPDATE ... SET seats seats - 1, version version 1 WHERE id ? AND version ?。在高并发场景下悲观锁更简单直接但要注意锁的粒度行锁和持有时间事务要短平快。异步通知发送邮件或站内信的通知操作我们使用Async注解或消息队列如RabbitMQ进行异步处理避免阻塞主业务流程提升响应速度。5.2 成绩录入与GPA计算成绩管理涉及批量操作和复杂的计算。我们为教师提供了一个批量录入成绩的界面后端对应一个批量处理服务。Service public class GradeEntryServiceImpl implements GradeEntryService { Autowired private CourseRegistrationMapper registrationMapper; Autowired private GradeConversionService conversionService; Autowired private StudentAcademicService academicService; Transactional(rollbackFor Exception.class) Override public void batchEnterGrades(Long courseOfferingId, ListGradeEntryDTO gradeEntries) { // 参数校验省略... for (GradeEntryDTO entry : gradeEntries) { CourseRegistration registration registrationMapper.selectByStudentAndCourse(entry.getStudentId(), courseOfferingId); if (registration null || !RegistrationStatus.APPROVED.equals(registration.getRegistrationStatus())) { throw new BusinessException(学生未注册该课程或状态异常); } // 1. 更新单条注册记录的成绩和绩点 registration.setFinalGrade(entry.getFinalGrade()); // 调用换算服务根据配置规则将成绩转换为绩点 BigDecimal gradePoint conversionService.convertGradeToPoint(entry.getFinalGrade(), courseOfferingId); registration.setGradePoint(gradePoint); registrationMapper.updateById(registration); // 2. 异步触发学生总GPA重算避免在循环中频繁更新学生主表 academicService.scheduleGpaRecalculation(entry.getStudentId()); } // 3. 记录成绩录入日志 logGradeEntryAction(courseOfferingId, gradeEntries.size()); } }设计考量批量操作与事务在一个事务中循环更新多条记录。如果成绩条数非常多如超过1000条需要考虑分批次提交避免长事务导致数据库连接占用过久。更好的做法是采用“任务队列”模式将批量操作拆分成多个小任务异步执行。GPA计算策略GPA平均绩点是学生总绩点除以总学分。我们并没有在每次成绩更新后立即重算而是通过scheduleGpaRecalculation方法将需要重算的学生ID放入一个延迟队列或缓存标记中由一个定时任务集中处理。这避免了在成绩录入高峰期对t_student表进行密集的UPDATE操作是一种典型的“最终一致性”设计。换算规则外置GradeConversionService内部查询t_grade_conversion_rule表根据课程类型、成绩输入值动态确定对应的绩点。这使得支持“A/B/C”、“百分制”、“通过/不通过”等多种成绩体系变得非常灵活。5.3 基于Spring Security的精细化权限管理我们使用Spring Security实现基于角色的访问控制RBAC并扩展了数据权限。Configuration EnableGlobalMethodSecurity(prePostEnabled true) // 启用方法级安全注解 public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() // API项目通常禁用CSRF .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态用JWT .and() .authorizeRequests() .antMatchers(/api/auth/login).permitAll() .antMatchers(/api/student/**).hasAnyRole(STUDENT, ADMIN) .antMatchers(/api/teacher/**).hasAnyRole(TEACHER, ADMIN) .antMatchers(/api/admin/**).hasRole(ADMIN) .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } // 配置JWT过滤器、密码编码器等省略... }在Service层我们使用PreAuthorize注解进行更细粒度的控制Service public class StudentServiceImpl implements StudentService { PreAuthorize(hasRole(ADMIN) or (hasRole(TEACHER) and securityService.canAccessStudent(#studentId, principal.username))) Override public StudentDetailVO getStudentDetail(Long studentId) { // 方法实现... } }这里的securityService.canAccessStudent(...)是一个自定义的权限表达式它会调用一个Spring Bean去判断当前登录的教师是否有权限查看这个学生的详情例如是否是这名学生的授课老师或导师。这样就实现了数据行级别的权限控制。6. 系统部署与性能优化实践项目开发完成后部署和优化是保证系统稳定运行的关键。6.1 部署架构我们采用了最经典的Linux服务器部署方案服务器一台阿里云ECS4核8G安装CentOS 7。中间件JDK 11MySQL 8.0 单独安装与应用服务器分离有条件应使用RDS。Nginx 作为反向代理和静态资源服务器。应用打包为可执行的JAR文件使用systemd服务管理实现开机自启和故障重启。6.2 关键性能优化措施数据库连接池使用HikariCP替代默认的Tomcat JDBC Pool配置合理的maximumPoolSize通常为CPU核心数 * 2 有效磁盘数并设置connectionTimeout和idleTimeout。SQL优化为所有高频查询条件字段建立复合索引。例如idx_status_program。避免SELECT *只查询需要的字段。对大数据量的分页查询使用WHERE id ? LIMIT ?的“游标分页”方式替代LIMIT offset, size后者在offset很大时性能极差。缓存策略本地缓存Caffeine缓存不经常变化的配置数据如国家列表、成绩换算规则。分布式缓存Redis缓存热点数据如学生基本信息、课程详情。同时用Redis实现分布式锁用于控制一些全局资源的并发访问虽然选课用了数据库锁但有些场景如定时任务调度Redis锁更合适。异步与批处理所有发送邮件、短信的通知全部走消息队列异步处理。报表生成、数据统计等耗时操作设计为后台任务提供进度查询。JVM调优根据服务器内存设置合理的堆大小-Xms和-Xmx并启用G1垃圾收集器。通过监控工具如Arthas观察GC日志避免频繁Full GC。7. 开发中遇到的典型问题与解决方案在实际编码和运维过程中我们踩过不少坑这里分享几个印象深刻的。7.1 时区问题导致的“幽灵”数据错误问题系统上线后偶尔有老师反馈在特定日期比如系统时间午夜前后查询当天注册的学生会漏掉一两条记录。排查经过日志分析发现问题是出在registration_time的查询上。我们的代码中用了LocalDate.now()来定义“今天”然后去数据库查registration_time ‘今天’的记录。但数据库的datetime字段存储的是UTC时间而应用服务器默认是东八区时间。当UTC时间过了0点即东八区早上8点LocalDate.now()获取的“今天”和数据库UTC时间的“今天”就错位了。解决统一时区在应用启动参数或代码中强制设置JVM和数据库连接的时区为Asia/Shanghai或根据业务需要设为UTC。# 启动JAR时 java -Duser.timezoneAsia/Shanghai -jar your-app.jar在代码中显式处理对于所有需要“按天”查询的业务使用数据库函数进行日期转换或者确保比较时使用相同的时区。// 使用数据库函数MySQL示例 String sql SELECT * FROM t_registration WHERE DATE(CONVERT_TZ(registration_time, 00:00, 08:00)) ?; // 或者在Java 8中使用ZonedDateTime ZonedDateTime todayStart LocalDate.now().atStartOfDay(ZoneId.of(Asia/Shanghai));心得时间处理是国际化系统最容易出错的地方之一。最佳实践是后端所有时间在内存和传输中用Instant时间戳或带时区的ZonedDateTime存入数据库时统一转为UTC时间。前端根据用户时区进行展示。7.2 循环依赖与事务失效问题在开发成绩录入后更新学生GPA的功能时我们最初在GradeEntryService中直接注入StudentService并调用其更新GPA的方法。后来发现StudentService中某个方法又调用了GradeEntryService的查询方法。启动时Spring报出了“BeanCurrentlyInCreationException”循环依赖错误。即使通过Lazy注解勉强启动在某个复杂事务场景下更新GPA的操作竟然没有回滚。排查这是典型的Spring Bean循环依赖和事务传播行为问题。事务是通过AOP代理实现的在存在循环依赖且方法调用发生在同一个类内部通过this调用时事务注解可能会失效。解决打破循环依赖重新设计服务边界。将“计算GPA”这个能力抽象成一个独立的、无状态的GpaCalculationService它只依赖DAO层不依赖其他业务Service。GradeEntryService和StudentService都去依赖这个GpaCalculationService。确保事务生效如果必须在一个Service内部调用自己的事务方法需要通过AopContext获取当前对象的代理实例来调用。((GradeEntryService) AopContext.currentProxy()).someTransactionalMethod();但这种方法不优雅更推荐方案1。7.3 分页查询性能瓶颈问题管理员后台有一个查看所有学生列表并支持复杂条件筛选的功能。当数据量达到10万级时使用MyBatis-Plus的Page对象进行limit offset, size分页在翻到后面几页时offset很大响应速度变得非常慢。解决采用“游标分页”或“seek method”。传统分页SELECT * FROM t_student WHERE ... ORDER BY id LIMIT 100000, 20。数据库需要先扫描并排序前100020条记录然后扔掉前100000条效率极低。游标分页SELECT * FROM t_student WHERE ... AND id 上一页最后一条记录的ID ORDER BY id LIMIT 20。这种查询可以利用id上的索引直接定位效率恒定。我们在前端配合改造不再传递“页码”而是传递“上一页最后一条记录的ID”或时间戳。后端接口相应调整。对于需要跳转到任意页的场景如管理后台这种模式不太友好但结合条件筛选和默认查看最新数据的需求游标分页在性能和体验上取得了很好的平衡。7.4 内存泄漏与OOM排查问题系统在平稳运行几周后突然在某个凌晨告警提示应用内存占用超过90%随后发生了OutOfMemoryError: Java heap space错误。排查首先登录服务器用jstat -gcutil pid查看GC情况发现老年代Old Gen占用率持续走高Full GC后回收效果很差说明存在内存泄漏。使用jmap -histo:live pid查看存活对象 histogram发现大量char[]和String对象疑似有缓存或集合类未释放。进一步使用jmap -dump:live,formatb,fileheap.hprof pid导出堆内存快照。用MATMemory Analyzer Tool分析heap.hprof文件。通过“Leak Suspects”报告发现一个最大的嫌疑对象是一个自定义的LocalCacheManager它内部使用了一个ConcurrentHashMap来缓存所有学生的完整信息并且没有设置过期时间或大小限制。随着学生数量增长这个Map越来越大最终撑爆了堆内存。解决引入缓存淘汰策略将缓存框架从简单的ConcurrentHashMap替换为 Caffeine 或 Guava Cache并设置合理的maximumSize和expireAfterWrite。区分缓存数据类型对于学生信息只缓存最常用的部分如id, name, number而不是整个包含关联对象的复杂DTO。增加监控在应用中暴露缓存命中率、当前大小的Metrics端点接入PrometheusGrafana进行监控做到提前预警。这次经历让我深刻体会到对于缓存“没有淘汰策略的缓存就是内存泄漏”。同时掌握一套完整的JVM问题排查工具链jps, jstat, jmap, MAT/VisualVM对于后端开发者至关重要。8. 项目总结与可扩展性思考回顾整个项目从技术角度看它巩固了我们在Spring Boot、MyBatis、数据库设计、缓存、事务、安全等方面的综合应用能力。从业务角度看它要求开发者深入理解留学生管理这个垂直领域的特殊规则并将这些规则转化为稳定、灵活的代码。这个系统在设计时也考虑了一些扩展点微服务化拆分如果业务量持续增长可以将“学生服务”、“课程服务”、“成绩服务”、“通知服务”拆分为独立的微服务通过Spring Cloud Alibaba等框架进行治理。国际化(i18n)深化目前只做了中英文界面。可以引入更专业的i18n框架将文案全部外置到属性文件甚至考虑支持日期、数字、货币的本地化格式化。工作流引擎集成对于“学生请假审批”、“课程变动申请”等流程可以集成Activiti或Flowable这样的工作流引擎让业务流程可视化、可配置。数据分析和报表可以集成Apache Superset或Metabase为管理人员提供更强大的自助式数据分析和可视化报表能力。最后给想要尝试类似项目的开发者一个建议在开始编码之前一定要花足够的时间与业务方学校的老师、管理员沟通理解他们每一个操作背后的真实意图和痛点。很多时候一个巧妙的数据结构设计或业务流程优化比使用更炫酷的技术更能提升系统的实用性和用户的满意度。这个留学生教务系统项目就是一次很好的业务驱动技术实践的旅程。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。