简介:本资源是一份面向软件工程专业学生、初级开发工程师及系统架构入门学习者的教学型PPT课件,聚焦软件系统框架与结构设计的核心方法论,解决实际开发中架构选型模糊、模块划分不清、质量保障缺位等典型问题。课件共1个PPTX文件(3.65MB),内容体系完整:第1章系统梳理软件工程全生命周期模型、质量保障机制与发展脉络;第2章深入解析系统架构定义、四大设计原则(性能/可靠性/可维护性/安全性)及模块化实现路径;第3章详解N层架构、面向对象设计(含UML与常用模式)、服务导向架构(SOA与微服务)等主流结构设计方法,并辅以数据层-业务逻辑层-表示层的分层案例。目录清晰、图文并茂、概念与实践结合紧密,适合作为课堂讲授辅助材料或自学体系化笔记。目前已有120人学习下载。
1. 这不是画架构图的作业,而是一份能过答辩、能跑通、能改出新项目的系统设计底盘
你手里的这份《软件工程中的系统框架与结构设计》材料,表面看是课程笔记或毕设提纲,但真正价值在于:它是一套可落地的系统设计底盘——不是教你怎么背“高内聚低耦合”,而是告诉你在真实项目里,当导师问“你这个三层结构怎么保证业务逻辑层不掺数据库连接?”,或者验收时发现“表示层调用业务方法居然要传12个参数”,该怎么从设计源头堵住这些漏洞。我带过三届某高校软件工程方向的课程设计,最常翻车的不是代码写错,而是结构失稳:模块边界模糊导致改一个登录逻辑,连带影响订单导出和权限校验;微服务拆分后服务间强依赖,本地调试得同时起5个进程;甚至UML类图画得再标准,一写代码就发现接口根本没法实现。这份方案就是为解决这类“设计-编码断层”而生的——它把抽象原则(比如“松耦合”)转化成具体约束(如“业务逻辑层禁止import任何DAO包”),把“性能原则”落实到可测指标(如“单次用户查询响应≤300ms,P95延迟≤450ms”),把“可维护性”变成检查清单(如“所有配置项必须集中于config模块,禁止硬编码在Service类中”)。适合正在做课设/毕设、需要交一份有设计深度、有实现痕迹、有验收依据的完整方案的同学,也适合刚入职想补全系统级思维的初级工程师。
2. 总体架构设计:三层结构与四个核心模块的职责铁律
2.1 为什么必须是“数据层-业务逻辑层-表示层”而非其他分法?
这不是教科书妥协,而是经过至少17个模拟项目X验证的最小可行结构。某跨平台系统早期尝试过“领域驱动+事件总线”架构,结果团队花了3周才让新手理解“领域服务”和“应用服务”的调用链路,最终回退到经典三层——不是因为它更先进,而是因为它的职责边界肉眼可见。数据层只做三件事:连接池管理、SQL执行、结果映射;业务逻辑层只做三件事:校验输入、编排原子操作、组装返回值;表示层只做三件事:接收请求、调用业务接口、格式化响应。这种切割直接对应到代码包结构:
// Java项目典型包结构(Spring Boot) com.example.system ├── data/ // 数据层:仅含JPA Entity、Repository接口、DataSource配置 │ ├── entity/User.java │ ├── repository/UserRepository.java │ └── config/DataSourceConfig.java ├── service/ // 业务逻辑层:仅含Service接口及实现,禁止出现@Mapper、@Repository注解 │ ├── UserService.java // 接口定义契约 │ └── impl/UserServiceImpl.java // 实现类,只调用Repository和Domain对象 ├── web/ // 表示层:仅含Controller、DTO、全局异常处理器 │ ├── controller/UserController.java │ ├── dto/UserRequest.java │ └── exception/GlobalExceptionHandler.java └── domain/ // 领域模型:纯POJO,无框架注解,不依赖任何外部包 └── User.java提示:很多同学把
domain包放在service下,这是重大隐患。领域对象一旦被Controller直接引用,就会导致DTO和Domain混用,后续加字段时极易引发序列化错误。我一般会强制要求domain包与data、service同级,且web包的pom.xml中排除对domain的依赖传递。
2.2 四个支撑模块:让三层结构真正“活”起来的设计锚点
光有三层只是骨架,必须嵌入四个支撑模块才能抗住真实压力。这四个模块不是可选项,而是我在某实验室重构旧系统时,用血泪经验确认的设计刚需:
| 模块名称 | 核心职责 | 关键设计约束 | 典型实现方式 |
|---|---|---|---|
| 配置中心模块 | 统一管理环境变量、开关参数、限流阈值 | 所有配置项必须通过@ConfigurationProperties注入,禁止System.getProperty()或@Value("${xxx}")硬编码 | Spring Cloud Config + Git后端,本地开发用application-dev.yml |
| 日志追踪模块 | 跨模块请求链路标识、关键节点耗时打点 | 每个HTTP请求必须生成唯一traceId,所有日志必须包含该ID;业务方法入口处记录START,出口处记录END | Sleuth + Logback MDC,自定义TraceFilter拦截请求 |
| 异常统一处理模块 | 将技术异常(如SQL异常)转化为业务语义明确的错误码 | 禁止在Service层try-catch后return null;所有异常必须继承BaseException并携带errorCode | @ControllerAdvice全局捕获,返回标准化JSON:{"code": "USER_NOT_FOUND", "message": "用户不存在"} |
| 监控告警模块 | 实时采集JVM、DB连接池、HTTP QPS等指标 | 每个模块必须暴露/actuator/metrics端点;关键指标(如DB连接池使用率)需配置Prometheus告警规则 | Spring Boot Actuator + Micrometer + Prometheus + Grafana |
这些模块的存在,直接决定了你的设计是否“可运维”。比如没有日志追踪模块,当用户反馈“提交订单失败但没报错”,你只能靠猜——是前端没传参?是库存校验超时?还是支付回调没收到?而有了traceId,一条命令就能查清全链路:grep "traceId=abc123" /var/log/app.log | sort -k3。
2.3 架构演进路线图:从单体到微服务的三个必经阶段
别被“微服务”这个词绑架。我见过太多课设项目强行拆服务,结果连本地调试都搞不定。真正的演进是渐进式的,每个阶段都有明确的准入门槛和退出标准:
阶段一:单体分层(当前必备)
目标:三层结构稳定运行,各层间无循环依赖。
验收标准:mvn compile能通过;mvn dependency:tree显示web模块不依赖data模块(仅通过service间接依赖);Postman调用/user/list返回正确JSON。注意:此阶段严禁引入Dubbo、Nacos等分布式组件。所谓“为微服务做准备”是伪命题——没跑通单体,谈何拆分?
阶段二:模块自治(课设进阶)
目标:将业务域(如用户、订单、商品)拆分为独立Maven模块,但仍在同一JVM运行。
验收标准:user-service模块的pom.xml中<scope>provided</scope>排除order-service的依赖;user-service的单元测试不启动H2数据库(用Mockito模拟Repository);order-service修改不影响user-service的CI构建。阶段三:服务化部署(毕设亮点)
目标:各模块独立打包、独立部署、通过HTTP/API网关通信。
验收标准:user-service和order-service分别部署在不同端口(如8081/8082);order-service调用用户服务必须走RestTemplate或FeignClient,禁止直接newUserServiceImpl;API网关(如Spring Cloud Gateway)能按路径路由(/api/user/** → 8081)。
记住:阶段一是底线,阶段二是加分项,阶段三是锦上添花。答辩时如果被问“为什么不用K8s”,直接回答:“当前单体已满足需求,过度设计会增加运维复杂度,违背软件工程‘降低开发成本’的核心目标”。
3. 模块化设计落地:接口契约、通信规范与重构安全边界
3.1 接口定义:用“契约先行”堵死模块越界调用
模块化不是给包起个好听的名字,而是用接口强制划清责任田。某开发者曾把“发送邮件”功能写在UserServiceImpl里,结果订单模块要发发货通知时,直接new EmailService()——这彻底破坏了模块边界。正确的做法是:所有跨模块调用必须通过接口,且接口定义必须位于被调用方模块。
以“用户注册成功后发送欢迎邮件”为例:
// 在 user-service 模块中定义(注意:不是在 order-service!) public interface EmailService { /** * 发送欢迎邮件 * @param toEmail 收件人邮箱(必须校验格式) * @param userName 用户名(长度限制:2-20字符) * @return true表示投递成功(不保证对方收到) */ boolean sendWelcomeEmail(String toEmail, String userName); } // 在 user-service 的 impl 包中实现 @Service public class EmailServiceImpl implements EmailService { @Override public boolean sendWelcomeEmail(String toEmail, String userName) { // 具体实现,可调用SMTP或第三方API return true; } } // 在 user-service 的 Controller 中调用 @RestController public class UserController { private final EmailService emailService; // 依赖注入 public UserController(EmailService emailService) { this.emailService = emailService; } @PostMapping("/register") public Result register(@RequestBody UserRegisterDTO dto) { // ... 业务逻辑 emailService.sendWelcomeEmail(dto.getEmail(), dto.getName()); // 合法调用 return Result.success(); } }关键设计说明:
- 接口
EmailService必须定义在user-service模块,这样order-service若想发邮件,就必须声明对user-service的Maven依赖,而不是自己造轮子;- 方法注释必须明确参数约束(如邮箱格式、用户名长度),这是契约的一部分,比代码更重要;
sendWelcomeEmail返回boolean而非void,因为调用方需要知道“是否尝试发送”,这直接影响后续流程(如发送失败是否重试)。
3.2 通信规范:RESTful API设计的五条军规
模块间通信不是“能通就行”,而是要建立可预测、可测试、可监控的协议。我给某公司制定的API规范,至今还在用:
路径必须体现资源层级
✅/api/v1/users/{id}/orders(用户ID下的订单列表)
❌/api/getUserOrders?id=123(动词+参数,无法缓存、难监控)状态码必须严格匹配语义
200 OK:成功获取资源201 Created:成功创建资源(如POST注册)400 Bad Request:客户端参数错误(如邮箱格式不对)401 Unauthorized:未认证(Token缺失)403 Forbidden:已认证但无权限(如普通用户访问管理员接口)404 Not Found:资源不存在(如查询ID=999的用户)422 Unprocessable Entity:参数校验失败(如密码少于6位)500 Internal Server Error:服务端未捕获异常(必须记录详细堆栈)
请求体必须用DTO隔离
// 错误:直接用Entity接收 @PostMapping("/register") public Result register(@RequestBody User user) { ... } // Entity含JPA注解,易引发反序列化漏洞 // 正确:专用DTO @PostMapping("/register") public Result register(@RequestBody UserRegisterDTO dto) { ... }响应体必须统一结构
{ "code": "SUCCESS", "message": "操作成功", "data": { "userId": 123, "token": "abc..." }, "timestamp": 1712345678900 }code用于前端判断业务状态(非HTTP状态码),data为业务数据,timestamp便于排查时序问题。必须提供OpenAPI文档
使用springdoc-openapi-ui自动生成/swagger-ui.html,且文档必须与代码同步更新。答辩时评委说“你这个接口怎么调用”,你直接打开Swagger点两下就能演示——这比讲十分钟原理管用。
3.3 重构安全边界:如何确保改代码不破结构?
模块化最大的价值是让重构变得安全。但前提是建立清晰的“不可触碰区”。我在某图像处理Demo中总结出三条红线:
红线一:表示层禁止持有业务对象引用
UserController可以调用UserService.register(),但绝不能new User()后塞进ModelAndView。必须用DTO转换:// 错误 model.addAttribute("user", user); // user是Entity,含Hibernate代理 // 正确 UserVO vo = new UserVO(); vo.setId(user.getId()); vo.setName(user.getName()); model.addAttribute("user", vo);红线二:业务逻辑层禁止出现SQL字符串
所有数据操作必须通过Repository接口,哪怕只是简单SELECT * FROM user WHERE id=?。某次重构时,我们发现OrderService里有段jdbcTemplate.query("SELECT ..."),结果切换数据库时全崩了——因为MySQL的LIMIT语法在Oracle里不生效。红线三:数据层禁止包含业务规则
UserRepository可以有findByEmail(String email),但绝不能有findActiveUsersByLastLoginDays(int days)。后者属于业务规则,应由UserService组合多个Repository调用实现。否则,当“活跃用户”定义变更(如增加“近30天有订单”条件),你得改Repository——这违反了单一职责。
常见问题排查:
现象:新增一个“用户标签”功能,结果订单模块的单元测试全挂了。
原因:UserEntity里加了@OneToMany关联标签,导致JPA加载用户时自动查标签表,而订单测试没配标签数据源。
解决:立即回滚Entity修改;在UserVO中添加List<String> tags字段,由UserService按需查询并组装——数据层保持纯净,业务逻辑层控制加载时机。现象:本地调试时
/user/list返回空数组,但数据库明明有数据。
原因:UserRepository.findAll()返回的是List<User>,而User的@Id字段类型是Long,但数据库里id是BIGINT UNSIGNED,MySQL驱动映射失败。
解决:在UserEntity中将id类型改为BigInteger,或在application.yml中添加spring.jpa.properties.hibernate.jdbc.use_streams_for_binary=true——但更治本的是:所有Entity主键必须与数据库类型严格一致,这是数据层的铁律。现象:
OrderService调用PaymentService.pay()后,事务不回滚。
原因:PaymentService是@Service,但pay()方法没加@Transactional,且OrderService的@Transactional默认传播行为是REQUIRED,导致支付失败时订单事务未感知。
解决:在PaymentService.pay()上加@Transactional(propagation = Propagation.REQUIRED),并确保两个服务在同一个Spring容器中——这再次证明:模块化不等于物理隔离,事务边界必须在设计阶段就画清楚。
4. 体系结构设计实战:从需求到UML再到可运行代码的闭环
4.1 需求到架构的翻译器:用“场景-能力-约束”三元组驱动设计
很多同学卡在“不知道怎么把需求变成架构图”。我的方法是:把每条需求拆成三个问题——它描述什么场景?系统需要提供什么能力?存在哪些硬性约束?以“用户可查看近30天订单列表”为例:
| 需求原文 | 场景 | 能力 | 约束 | 架构决策 |
|---|---|---|---|---|
| “用户可查看近30天订单列表” | 用户登录后点击“我的订单” | 返回按时间倒序排列的订单摘要(订单号、金额、状态、时间) | ① 响应时间≤1s(P95) ② 订单量可能达百万级 ③ 不需要实时精确(允许5分钟延迟) | ▶ 数据层:订单表加create_time索引▶ 业务层: OrderService.listRecentOrders(userId)方法,内部用Pageable分页▶ 表示层: GET /api/v1/orders?days=30&page=1&size=20,返回List<OrderSummaryVO> |
这个过程强迫你思考技术选型背后的理由。比如为什么用分页而不是OFFSET?因为约束②(百万级数据)下,OFFSET 100000会导致MySQL全表扫描。为什么允许5分钟延迟?因为约束③意味着可以用Redis缓存结果,避免每次请求都查库。
4.2 UML建模:只画三张图,每张都服务于代码生成
UML不是为了画得好看,而是为了减少沟通成本。我只要求三张图,且每张都能直接生成代码骨架:
类图(Class Diagram):聚焦核心领域对象与关系
只画User、Order、Product三个类,以及它们之间的关联(如Order1对多OrderItem)。绝不画工具类(如DateUtils)、框架类(如SpringBootApplication)。用PlantUML生成代码:@startuml class User { +Long id +String email +String password } class Order { +Long id +Long userId +BigDecimal totalAmount } User "1" --> "0..*" Order : places @enduml工具可自动生成Java类(
User.java,Order.java)和基础Mapper接口。序列图(Sequence Diagram):描述关键用例的交互流程
只画“用户下单”这个最复杂的用例,标注每一步的模块归属:@startuml actor User participant "Web: UserController" as web participant "Service: OrderService" as service participant "Data: OrderRepository" as data User -> web: POST /orders {items:[{pid:1,q:2}]} web -> service: createOrder(userId, items) service -> data: save(order) data --> service: order.id service --> web: OrderVO web --> User: 201 Created {orderId:123} @enduml这张图直接对应到代码分层:箭头起点是调用方模块,终点是被调用方模块,中间的
createOrder方法签名就是OrderService的接口定义。部署图(Deployment Diagram):明确运行时组件位置
用容器化思维画,而非服务器型号:@startuml node "Frontend" as fe { [Vue App] } node "Backend" as be { [Spring Boot App] } node "Database" as db { [MySQL] } node "Cache" as cache { [Redis] } fe --> be : HTTPS be --> db : JDBC be --> cache : Jedis @enduml这张图决定了
application.yml里数据库URL写mysql://db:3306/app,而不是localhost——因为Docker Compose中服务名就是db。
4.3 设计模式落地:工厂模式解决“对象创建污染”,观察者模式解耦“业务副作用”
设计模式不是炫技,而是解决特定痛点。两个最常用、最容易写错的模式:
工厂模式:隔离对象创建逻辑,避免Service类变“上帝类”
某次重构前,OrderService里有12个new XXXService(),导致单元测试必须Mock所有依赖。改成工厂后:// 定义工厂接口 public interface PaymentFactory { PaymentProcessor getProcessor(PaymentMethod method); } // 实现类根据策略返回不同处理器 @Service public class DefaultPaymentFactory implements PaymentFactory { @Override public PaymentProcessor getProcessor(PaymentMethod method) { switch (method) { case ALIPAY: return new AlipayProcessor(); case WECHAT: return new WechatProcessor(); default: throw new IllegalArgumentException("Unsupported method"); } } } // Service中只依赖工厂 @Service public class OrderService { private final PaymentFactory paymentFactory; public OrderService(PaymentFactory paymentFactory) { this.paymentFactory = paymentFactory; } public void processPayment(Long orderId, PaymentMethod method) { PaymentProcessor processor = paymentFactory.getProcessor(method); // 创建逻辑外移 processor.execute(orderId); } }设计说明:工厂模式的价值在于把“new”从业务逻辑中剥离。当新增
APPLE_PAY时,只需改工厂实现,OrderService完全不用动——这才是可维护性的本质。观察者模式:让“发邮件”“写日志”“推消息”这些副作用不污染主流程
如果OrderService.createOrder()里直接调emailService.send(),那每次发邮件失败都会导致订单创建失败。用观察者解耦:// 定义事件 public class OrderCreatedEvent { private final Long orderId; public OrderCreatedEvent(Long orderId) { this.orderId = orderId; } // getter... } // 定义监听器 @Component public class EmailNotificationListener implements ApplicationListener<OrderCreatedEvent> { @Override public void onApplicationEvent(OrderCreatedEvent event) { emailService.sendOrderConfirm(event.getOrderId()); // 异步执行,失败不阻塞主流程 } } // 在Service中发布事件 @Service public class OrderService { private final ApplicationEventPublisher eventPublisher; public void createOrder(...) { // ... 创建订单逻辑 eventPublisher.publishEvent(new OrderCreatedEvent(order.getId())); // 主流程干净利落 } }关键点:监听器必须是
@Component且实现ApplicationListener,这样Spring容器能自动注册;事件发布用ApplicationEventPublisher,而非手动遍历监听器列表——这是框架提供的可靠性保障。
5. 系统集成与部署:从本地启动到生产环境的四步通关
5.1 本地开发环境:用Docker Compose一键拉起全链路
课设/毕设最头疼的是“在我电脑上好好的,换台电脑就跑不了”。解决方案:用Docker Compose定义所有依赖,让环境成为代码的一部分。某跨平台系统的docker-compose.yml精简版:
version: '3.8' services: app: build: . ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=dev - DB_URL=jdbc:mysql://db:3306/app?useSSL=false - REDIS_URL=redis://cache:6379 depends_on: - db - cache db: image: mysql:8.0 environment: - MYSQL_ROOT_PASSWORD=root - MYSQL_DATABASE=app volumes: - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql cache: image: redis:7-alpine command: redis-server --appendonly yes volumes: - redis-data:/data volumes: redis-data:配套的Dockerfile(Spring Boot项目):
FROM openjdk:17-jdk-slim VOLUME /tmp ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} app.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]设计说明:
depends_on确保db和cache先启动,但Docker不保证服务就绪(MySQL可能还在初始化),所以app启动类中必须加入健康检查重试逻辑;volumes挂载SQL初始化脚本,避免手动建库;REDIS_URL用服务名cache而非localhost,这是Docker网络的关键——容器间通信用服务名,宿主机访问用localhost。
5.2 CI/CD流水线:用GitHub Actions实现“提交即验证”
答辩前最怕改完代码忘测试。自动化流水线是底线保障。某实验室的.github/workflows/ci.yml:
name: CI Pipeline on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' - name: Build with Maven run: mvn -B clean package -DskipTests - name: Run Unit Tests run: mvn test - name: Check Code Quality run: mvn sonar:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.token=${{ secrets.SONAR_TOKEN }} deploy-dev: needs: test if: github.event_name == 'push' && github.ref == 'refs/heads/dev' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Deploy to Dev Server uses: appleboy/scp-action@master with: host: ${{ secrets.DEV_HOST }} username: ${{ secrets.DEV_USER }} key: ${{ secrets.DEV_SSH_KEY }} source: "target/*.jar" target: "/opt/app/"关键设计点:
needs: test确保只有测试通过才部署;if条件限定仅dev分支推送才触发部署,避免误操作;scp-action直接上传jar包,比用Ansible更轻量——课设不需要复杂运维,够用就好。
5.3 生产环境部署:Nginx反向代理与Spring Boot Actuator监控
答辩时评委常问“怎么保证线上稳定?”。答案不在PPT里,在真实的监控配置中。某电商系统生产部署要点:
Nginx配置(
/etc/nginx/conf.d/app.conf):upstream backend { server 127.0.0.1:8080 max_fails=3 fail_timeout=30s; # 可加第二台机器实现负载均衡 # server 127.0.0.1:8081 max_fails=3 fail_timeout=30s; } server { listen 80; server_name your-domain.com; location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 暴露Actuator端点(仅内网可访问) location /actuator/ { allow 10.0.0.0/8; # 内网IP段 deny all; proxy_pass http://backend/actuator/; } }为什么必须用Nginx?因为Spring Boot内置Tomcat不适合生产:无法做SSL终止、无法限流、无法健康检查。Nginx的
max_fails参数让故障实例自动下线,这是高可用的基础。Spring Boot Actuator配置(
application-prod.yml):management: endpoints: web: exposure: include: health,info,metrics,prometheus,loggers,threaddump endpoint: health: show-details: when_authorized info: git: mode: full logging: level: root: INFO com.example.system: DEBUG验收技巧:答辩时打开
http://your-domain.com/actuator/health,显示{"status":"UP"};再打开/actuator/metrics/jvm.memory.max,能看到内存上限——这比说“我用了监控”有力一万倍。
6. 验收与答辩:用“可验证证据链”代替空泛描述
6.1 验收清单:五类证据缺一不可
答辩不是讲故事,而是交证据。我要求学生必须准备以下五类可验证材料,缺一项就扣分:
| 证据类型 | 具体内容 | 验收方式 | 为什么重要 |
|---|---|---|---|
| 架构图证据 | PlantUML源码(.puml文件)+ 生成的PNG图 | 导师现场打开.puml文件,用VS Code PlantUML插件重新渲染,确认与提交图一致 | 证明图不是PPT画的,而是从代码/设计中导出的 |
| 接口证据 | Swagger UI截图(含/v3/api-docs返回的JSON)+ Postman集合导出文件(.json) | 导师用Postman导入集合,点击“Send”验证接口返回 | 证明API真实存在且可调用,不是纸上谈兵 |
| 部署证据 | docker-compose ps命令输出截图 +curl -I http://localhost:8080/actuator/health返回 | 导师SSH到学生服务器,执行相同命令 | 证明环境真能跑,不是本地虚拟机快照 |
| 测试证据 | mvn test控制台输出截图(含Tests run: 42, Failures: 0, Errors: 0)+ JaCoCo覆盖率报告(target/site/jacoco/index.html) | 导师检查覆盖率报告,确认service包覆盖率≥80% | 证明代码经过测试,且重点覆盖了业务逻辑层 |
| 安全证据 | mvn dependency:tree | grep "spring-security"输出 +application.yml中management.endpoints.web.exposure.include配置截图 | 导师检查是否启用安全配置,敏感端点是否受限 | 证明考虑了安全性,不是裸奔上线 |
注意:所有截图必须带时间戳(系统右下角),且不能是手机拍摄——要用系统自带截图工具。我见过学生用美图秀秀P图,结果时间戳字体和系统不一致,当场被识破。
6.2 答辩话术:用“问题-方案-证据”三段式回应质疑
评委提问往往直击弱点。不要解释,直接用证据链反击。例如:
问题:“你这个微服务拆分合理吗?订单服务调用用户服务,延迟会不会很高?”
回应:“这是个关键问题。我们的方案是:① 用FeignClient声明式调用,内置Ribbon负载均衡;② 设置超时:feign.client.config.default.connectTimeout=3000;③ 关键路径加缓存——用户基本信息从Redis读取,TTL=30分钟。证据是:Postman压测/orders接口,100并发下P95延迟420ms(展示JMeter报告截图),且/actuator/metrics/cache.user.info.hit指标显示缓存命中率92%。”问题:“三层结构里,业务逻辑层怎么保证不掺杂数据库操作?”
回应:“我们用Maven依赖约束强制隔离。证据是:pom.xml中web模块的<dependency>里,user-service的scope是compile,而user-data的scope是provided(展示pom.xml截图);执行mvn dependency:tree -f web/pom.xml,输出中不出现user-data(展示终端截图)。任何违反都会导致编译失败。”问题:“日志追踪怎么实现的?能查到一次请求的完整链路吗?”
回应:“用Sleuth生成traceId,所有日志通过Logback MDC注入。证据是:在UserController中加一行log.info("Start processing");,然后用curl -H "X-B3-TraceId: abc123" http://localhost:8080/api/users/1发起请求,再grep "abc123" /var/log/app.log(展示日志截图),可以看到Controller、Service、Repository三层日志都带有相同traceId。”
6.3 我的血泪教训:从那以后,我每次设计都强制走一遍“五分钟验证”
带过这么多项目,最深刻的教训是:设计文档写得再漂亮,不跑通第一行代码,就是废纸。我给自己定了一条铁律:任何新模块设计完成,必须在5分钟内完成三件事——
- 用PlantUML生成类图,并确认生成的Java类能编译通过;
- 在
application-dev.yml中配好数据库连接,执行mvn spring-boot:run,看到控制台输出Started Application in X seconds; - 用Postman调通第一个接口(如
GET /actuator/health),返回{"status":"UP"}。
如果哪一步卡超过5分钟,立刻停下手头工作,回溯设计——要么是模块划分不合理(比如把数据源配置写在了service模块),要么是依赖版本冲突(比如Spring Boot 3.x和老版MyBatis不兼容)。这个习惯让我避开了90%的“设计返工”。希望帮到你。
本文还有配套的精品资源,点击获取