1. 项目背景与意义

随着软件工程教育的普及和深入,高校及培训机构对软件工程实训环节的管理需求日益增长。传统的实训管理方式多依赖人工记录、Excel表格或单机系统,存在数据孤岛、流程割裂、效率低下、难以量化评估等问题。

本项目旨在设计并实现一个基于Spring Boot微服务架构的软件工程实训管理系统。该系统将实训过程(如项目立项、任务分配、进度跟踪、代码提交、文档管理、成绩评定)进行数字化、流程化管理,其意义在于:

  • 提升管理效率:自动化流程,减少教师和学生的重复性管理工作。
  • 促进过程透明:实时跟踪项目进度与成员贡献,使实训过程可视化。
  • 强化能力培养:通过系统化的任务分解、代码审查和文档管理,模拟企业级软件开发流程。
  • 积累教学数据:为教学评估、课程改进提供数据支撑。
  • 技术实践价值:项目本身采用主流的微服务、前后端分离等技术栈,为学生提供了一个绝佳的、贴近工业界的全栈开发实践案例。

2. 技术栈选型

系统采用前后端分离的微服务架构,技术选型兼顾成熟度、社区活跃度与开发效率。

2.1 后端技术栈

  • 核心框架:Spring Boot 2.7.x / 3.x
  • 微服务架构:Spring Cloud Alibaba (Nacos, Sentinel, Seata) 或 Spring Cloud Netflix (Eureka, Hystrix, Zuul)
  • 服务通信:OpenFeign (声明式HTTP客户端), Ribbon (客户端负载均衡)
  • API网关:Spring Cloud Gateway
  • 配置中心:Nacos Config / Apollo
  • 服务注册与发现:Nacos / Eureka
  • 熔断与限流:Sentinel / Hystrix
  • 分布式事务:Seata (可选,用于强一致性场景)
  • ORM框架:MyBatis-Plus (简化CRUD操作)
  • 数据库:MySQL 8.0 (主业务数据), Redis (缓存、会话管理)
  • 消息队列:RabbitMQ / RocketMQ (异步解耦,如通知发送、日志处理)
  • 安全框架:Spring Security + JWT (JSON Web Token) 实现认证与授权
  • API文档:Knife4j (集成Swagger UI, 美观易用)
  • 构建工具:Maven / Gradle
  • 容器化:Docker, Docker Compose (服务编排)

2.2 前端技术栈

  • 核心框架:Vue 3 (Composition API) 或 React 18
  • 构建工具:Vite
  • UI组件库:Element Plus (Vue) 或 Ant Design (React)
  • 状态管理:Pinia (Vue) 或 Redux Toolkit (React)
  • 路由:Vue Router 或 React Router
  • HTTP客户端:Axios
  • 可视化图表:ECharts / AntV G2

2.3 运维与部署

  • 版本控制:Git (GitLab / Gitee)
  • 持续集成/持续部署 (CI/CD):Jenkins / GitLab CI
  • 容器编排:Kubernetes (K8s) (生产环境可选)
  • 监控与日志:Spring Boot Admin, Prometheus + Grafana, ELK Stack (Elasticsearch, Logstash, Kibana)

3. 系统核心模块设计

系统可拆分为以下微服务,每个服务独立部署、独立数据库。

  • 用户服务 (user-service):负责用户注册、登录、个人信息管理、角色权限管理。
  • 项目管理服务 (project-service):实训项目的创建、分组、成员管理、项目状态跟踪。
  • 任务服务 (task-service):项目内任务的创建、分配、进度更新、验收。
  • 代码仓库服务 (code-service):集成Git,提供代码提交、分支管理、合并请求、代码审查功能。
  • 文档服务 (document-service):项目文档、设计文档、周报的上传、版本管理、在线预览。
  • 成绩评定服务 (grade-service):评分规则设定,教师对学生/小组的任务完成度、代码质量、文档质量进行多维度评分。
  • 通知服务 (notification-service):站内信、邮件、WebSocket实时通知(如任务分配、截止提醒、评审结果)。
  • 网关服务 (gateway):统一入口,路由转发,权限校验,限流熔断。

4. 核心代码示例

4.1 用户服务 - JWT认证与授权

JWT工具类 (JwtUtil.java)

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtUtil {
    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expiration}")
    private Long expiration;

    public String generateToken(String username, String role) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("role", role);
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    public Claims parseToken(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }

    public boolean isTokenExpired(String token) {
        return parseToken(token).getExpiration().before(new Date());
    }

    public String getUsernameFromToken(String token) {
        return parseToken(token).getSubject();
    }

    public String getRoleFromToken(String token) {
        return (String) parseToken(token).get("role");
    }
}

4.2 项目管理服务 - 创建项目 (RESTful API)

控制器 (ProjectController.java)

import com.example.projectservice.dto.ProjectCreateRequest;
import com.example.projectservice.dto.ProjectDTO;
import com.example.projectservice.service.ProjectService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@RestController
@RequestMapping("/api/projects")
@RequiredArgsConstructor
public class ProjectController {

    private final ProjectService projectService;

    @PostMapping
    @PreAuthorize("hasRole('TEACHER')") // 仅教师可创建项目
    public ResponseEntity<ProjectDTO> createProject(@Valid @RequestBody ProjectCreateRequest request,
                                                    @RequestHeader("X-User-Id") String creatorId) {
        ProjectDTO createdProject = projectService.createProject(request, creatorId);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdProject);
    }

    @GetMapping("/{projectId}")
    @PreAuthorize("hasAnyRole('TEACHER', 'STUDENT')")
    public ResponseEntity<ProjectDTO> getProject(@PathVariable String projectId) {
        ProjectDTO project = projectService.getProjectById(projectId);
        return ResponseEntity.ok(project);
    }
}

4.3 任务服务 - 服务间调用 (OpenFeign)

Feign客户端接口 (UserServiceClient.java)

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;

@FeignClient(name = "user-service", path = "/api/internal/users")
public interface UserServiceClient {

    @GetMapping("/{userId}/exists")
    Boolean checkUserExists(@PathVariable("userId") String userId,
                            @RequestHeader("X-Internal-Token") String internalToken);

    @GetMapping("/{userId}/name")
    String getUserName(@PathVariable("userId") String userId,
                       @RequestHeader("X-Internal-Token") String internalToken);
}

在任务服务中调用

@Service
@RequiredArgsConstructor
public class TaskServiceImpl implements TaskService {
    private final UserServiceClient userServiceClient;

    @Override
    public TaskDTO assignTask(TaskAssignRequest request) {
        // 1. 验证被分配用户是否存在
        Boolean userExists = userServiceClient.checkUserExists(request.getAssigneeId(), internalToken);
        if (Boolean.FALSE.equals(userExists)) {
            throw new BusinessException("被分配用户不存在");
        }
        // 2. 创建任务...
        // 3. 发送通知...
        return taskDTO;
    }
}

4.4 网关服务 - 全局过滤器 (认证与路由)

全局认证过滤器 (AuthGlobalFilter.java)

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final JwtUtil jwtUtil;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();
        // 放行登录、注册等公开接口
        if (path.startsWith("/api/auth/login") || path.startsWith("/api/auth/register")) {
            return chain.filter(exchange);
        }

        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }

        token = token.substring(7);
        try {
            String username = jwtUtil.getUsernameFromToken(token);
            String role = jwtUtil.getRoleFromToken(token);
            // 将用户信息添加到请求头,传递给下游服务
            exchange.getRequest().mutate()
                    .header("X-User-Id", username)
                    .header("X-User-Role", role)
                    .build();
        } catch (Exception e) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -100;
    }
}

更多推荐