简介本资源是一份完整的图书馆管理系统UML面向对象分析与设计文档面向高校信息管理与信息系统、软件工程等专业学生及初学者解决课程设计中UML建模实践能力薄弱、用例与类图设计缺乏规范参考的问题。文档基于山东工商学院实际课题系统梳理了读者、图书管理员、系统管理员三类参与者及其20核心用例涵盖用例图、参与者关系表、用例描述含前置/后置条件、基本与异常事件流并深入说明安全性视图机制、权限分级与完整性主外键、Check约束、触发器设计要点。资源为单个PDF文件大小803KB内容结构清晰含需求分析、系统目标、功能模块划分读者/书籍/借阅/系统管理、子系统说明及完整用例模型图示。目前已有135人学习下载可直接用于课程设计报告撰写、UML建模作业参考或毕业设计前期建模范例。1. 这不是一份“交作业式”的UML文档它是一套可落地的图书馆系统建模骨架含8个核心类、5类参与者权限、4种动态交互逻辑能直接喂给Spring Boot或Django项目做领域建模起点你手头这份《图书馆管理系统UML设计.pdf》不是课程结课PPT里那种画满箭头却跑不通的示意图。它是山东工商学院信管专业学生基于真实业务流拆解出的完整建模产物——从读者借书时“扫描条码→校验借阅资格→生成记录→更新库存”这一连串动作到管理员删除图书后触发的级联状态变更再到系统管理员重置权限时的视图隔离机制全部用UML五类图用例图、类图、顺序图、活动图、参与者关系闭环表达。它不教你画图工具怎么点菜单而是告诉你为什么“罚款记录类”必须与“借阅记录类”双向关联为什么“读者借阅状态类”要单独抽离成独立实体而非嵌入读者信息为什么图书管理员处理还书时顺序图里必须有“验证是否超期→弹窗提示→收取罚款→更新状态”这四步不可跳过这些决策背后是数据库完整性约束如外键级联、权限控制粒度按角色切分CRUD、以及业务异常路径如丢失书籍需冻结读者账号的真实映射。如果你正用Java/Python开发一个轻量级图书馆后台或者带学生做课程设计需要快速搭出MVC三层结构这份文档就是你省掉3天需求梳理2天ER建模的现成骨架——类名、属性、关系、职责全在连check约束字段如“允许借阅最大数≤5”“借阅期限≤30天”都已标注。别再从零画用例图了先把它导入StarUML或PlantUML跑一遍再对照你的SQL建表语句查漏补缺。2. 从用例图到类图如何把“读者借书”这个动作翻译成8个类之间的协作契约2.1 用例图不是装饰画它定义了系统边界的三重过滤机制这份文档的用例图第4-5页表面看只是几个椭圆加连线实则暗藏三重过滤逻辑参与者维度过滤读者Reader A1只能触发“查询书籍”“借书”“还书”等6个用例而图书管理员Librarian A2额外拥有“添加图书”“删除读者”等12个操作权限系统管理员Administrator A3仅保留对管理员账号的CRUD权限。这种分离不是拍脑袋定的而是对应现实岗位权责——普通读者不可能删书目管理员不该重置系统级密码。前置条件过滤每个用例都明确标注前置条件如“读者查询图书”要求“通过ISBN/ISSN号查找”“读者信息查询”强制“读者成功登录系统”。这意味着你在写Controller层时必须在方法入口处校验PreAuthorize(hasRole(READER))和if (!user.isAuthenticated()) throw new AuthException()否则UML里的“前置条件”就成摆设。异常流显式声明所有用例均包含“异常事件流”如“没有操作权限给出错误提示”这直接对应Spring的ExceptionHandler设计——你得为AccessDeniedException专门写一个返回403 JSON的全局处理器而不是让框架抛500页面。提示别急着画图先用Excel拉一张“用例-参与者-前置条件-异常流”对照表确认每个用例的触发者、准入门槛、失败兜底方案。这是后续类图设计的输入源。2.2 类图不是属性罗列8个类的职责边界由业务动词决定文档第14-15页的类图列出了8个类但真正关键的是它们的动词化职责即每个类该响应什么消息BookInfo图书信息类只负责“提供图书元数据”不处理借阅逻辑。它的方法应是getISBN()、getPublisher()而非borrowBy(readerId)——后者属于BorrowRecord类的范畴。BorrowRecord借阅记录类承担状态流转核心方法包括createRecord(readerId, bookId)、setDueDate(days30)、markAsOverdue()。注意其属性含应还时间dueDate和归还时间returnDate二者为空/非空组合直接驱动超期判断逻辑。ReaderBorrowStatus读者借阅状态类这是易被忽略的策略类。它不存具体借阅行为而是定义规则“允许借阅最大数5”“持有最长期限30天”。系统初始化时加载此配置借书前调用validateBorrowLimit(readerId)校验避免硬编码在Service里。下面这段Python伪代码演示了类间协作如何落地以借书为例# 基于UML类图设计的领域服务层逻辑 class BorrowService: def borrow_book(self, reader_id: str, book_id: str) - bool: # 1. 获取读者状态策略ReaderBorrowStatus status ReaderBorrowStatus.get_by_reader_type(reader_id) if not status.validate_borrow_limit(reader_id): raise BorrowLimitExceededException() # 2. 检查图书可用性BookInfo BorrowRecord联合查询 book BookInfo.find_by_id(book_id) if book.status ! IN_STOCK: raise BookNotAvailableException() # 3. 创建借阅记录BorrowRecord record BorrowRecord.create( reader_idreader_id, book_idbook_id, due_datedatetime.now() timedelta(daysstatus.max_holding_days) ) # 4. 更新图书状态BookInfo book.update_status(BORROWED) # 5. 记录日志隐含在UML活动图“存储借书记录”步骤中 self._log_borrow_event(record) return True这段代码严格遵循UML中“借书用例活动图”第12页的流程先验证→再查资源→创建记录→更新状态→持久化。其中ReaderBorrowStatus的引入正是为了解决文档1.2.3节强调的“完整性要求”——把业务规则从代码里抽离成可配置对象避免if (readerType STUDENT) maxBooks 5这类硬编码。2.3 动态模型不是动画片顺序图揭示了跨层调用的时序陷阱文档第17-19页的5个顺序图用户登录、读者查书、借书、还书、图书管理暴露了一个致命细节所有跨层调用都存在隐式事务边界。以“借书用例顺序图”为例图中图书管理员生命线向图书借阅管理界面发送“输入读者账号”消息后者再向读者信息查询接着向借阅记录查询……最终借阅记录返回结果后图书借阅管理界面才执行“借书处理”。这意味着如果读者信息查询成功但借阅记录查询因网络超时失败整个流程必须回滚——否则出现“界面显示可借实际未生成记录”的数据不一致。因此在Spring Boot中你必须将BorrowService.borrow_book()标记为Transactional且传播行为设为REQUIRED默认值。更关键的是UML图中借阅记录类被设计为独立实体而非BookInfo的子属性这决定了DAO层必须有BorrowRecordRepository接口且其save()方法需在事务内执行。若你图省事把借阅逻辑塞进BookInfoService就会违反UML定义的职责分离原则导致后续扩展预订功能时牵一发而动全身。3. 把UML类图转成数据库表主外键、Check约束、索引设计的实操清单3.1 表结构映射8个类如何变成7张物理表含视图UML中的8个类并非一对一映射为8张表。根据文档1.2.3节“完整性要求”和2.1.1节类属性描述我们做如下裁剪BookInfo图书信息类→book_info表含book_id(PK),isbn,title,author,publisher,price,publish_date,category,keywords,copy_count,locationReaderInfo读者信息类→reader_info表含reader_id(PK),name,gender,student_id,reader_type_id,college,major,grade,register_dateLibrarian图书管理员类→librarian表含librarian_id(PK),name,gender,password_hash,role_level,phone,locationBorrowRecord借阅记录类→borrow_record表含record_id(PK),reader_id(FK),book_id(FK),book_title,author,borrow_date,return_date,due_date,librarian_id(FK)FineRecord罚款记录类→fine_record表含fine_id(PK),book_id(FK),reader_id(FK),book_title,borrow_date,return_date,amount,statusReaderBorrowStatus读者借阅状态类→reader_borrow_status表含type_id(PK),type_name,max_books,max_holding_days,card_validity_daysFineStandard罚款标准类→fine_standard表含standard_id(PK),standard_name,applicable_to,base_amount,per_day_rate注意BookInfo类属性中的“图书编号”和“图书索书号”在表中合并为book_id主键因UML未说明二者差异BorrowRecord类属性含图书名和作者这是冗余设计——为避免JOIN性能损耗按UML原文保留但需在插入时同步book_info.title和book_info.author。3.2 约束实现用SQL把UML的“完整性要求”钉死在数据库层文档1.2.3节强调“可通过建立主、外键使用check约束或者通过使用触发器和级联更新”我们逐条落实UML约束点SQL实现说明“读者借阅最大数≤5”ALTER TABLE reader_borrow_status ADD CONSTRAINT chk_max_books CHECK (max_books 5);直接限制配置表取值防止管理员误设“借阅期限≤30天”ALTER TABLE reader_borrow_status ADD CONSTRAINT chk_max_days CHECK (max_holding_days 30);同上业务规则在DB层固化borrow_record.reader_id→reader_info.reader_idALTER TABLE borrow_record ADD CONSTRAINT fk_reader FOREIGN KEY (reader_id) REFERENCES reader_info(reader_id) ON DELETE CASCADE;ON DELETE CASCADE确保删除读者时自动清理其借阅记录对应UML活动图“删除读者”用例borrow_record.book_id→book_info.book_idALTER TABLE borrow_record ADD CONSTRAINT fk_book FOREIGN KEY (book_id) REFERENCES book_info(book_id) ON UPDATE CASCADE;ON UPDATE CASCADE保证图书编号变更时借阅记录同步更新避免孤儿记录fine_record.amount 0ALTER TABLE fine_record ADD CONSTRAINT chk_fine_amount CHECK (amount 0);防止插入负罚款金额-- 示例创建borrow_record表并施加完整约束 CREATE TABLE borrow_record ( record_id SERIAL PRIMARY KEY, reader_id VARCHAR(20) NOT NULL, book_id VARCHAR(20) NOT NULL, book_title VARCHAR(200), author VARCHAR(100), borrow_date DATE NOT NULL DEFAULT CURRENT_DATE, return_date DATE, due_date DATE NOT NULL, librarian_id VARCHAR(20) NOT NULL, -- 外键约束 CONSTRAINT fk_reader FOREIGN KEY (reader_id) REFERENCES reader_info(reader_id) ON DELETE CASCADE, CONSTRAINT fk_book FOREIGN KEY (book_id) REFERENCES book_info(book_id) ON UPDATE CASCADE, CONSTRAINT fk_librarian FOREIGN KEY (librarian_id) REFERENCES librarian(librarian_id), -- Check约束还书日期不能早于借书日期 CONSTRAINT chk_return_after_borrow CHECK (return_date IS NULL OR return_date borrow_date), -- Check约束应还日期必须晚于借书日期 CONSTRAINT chk_due_after_borrow CHECK (due_date borrow_date) );这段SQL不仅建表更把UML中隐含的业务规则如“return_date不能早于borrow_date”转化为数据库强制约束。若应用层代码试图插入return_date2023-01-01而borrow_date2023-01-02PostgreSQL会直接报错violates check constraint chk_return_after_borrow无需Java层二次校验。3.3 索引优化针对UML用例高频查询路径布防UML用例图第4页显示“读者查询图书”“图书管理员查询书籍”“读者信息查询”是最高频操作。据此设计复合索引查询场景UML用例ID索引语句设计依据读者按ISBN查书U1CREATE INDEX idx_book_isbn ON book_info(isbn);用例U1明确“通过ISBN/ISSN号查找”ISBN是唯一标识单列索引足够图书管理员按作者查书U5CREATE INDEX idx_book_author ON book_info(author);U5用例流含“按作者查询”作者可能重复需索引加速借阅记录按读者ID状态查隐含在U3/U4CREATE INDEX idx_borrow_reader_status ON borrow_record(reader_id, return_date) WHERE return_date IS NULL;U3/U4需快速定位某读者“未还书”记录用部分索引过滤已还书行提升查询效率3倍以上罚款记录按读者ID查隐含在U1异常流CREATE INDEX idx_fine_reader ON fine_record(reader_id) WHERE status PENDING;缴纳罚款前需查待处理罚款部分索引聚焦活跃数据提示别迷信“所有WHERE字段都加索引”。UML中BookInfo类有11个属性但只有isbn、author、publisher在用例中被明确作为查询条件U1/U5其余如keywords、location若无高频查询需求加索引反而拖慢INSERT。4. 避坑UML文档里没写的5个血泪经验让你少踩3天调试坑4.1 现象用例图中“读者”和“图书管理员”都可执行“查询书籍”但实际开发时发现权限校验总失效原因UML用例图第4页将“查询书籍”同时分配给Reader和Librarian但未说明查询范围差异。文档1.2.2节功能需求写“以书名、作者、出版社等信息进行图书检索”却没提读者只能查公开字段书名、作者而管理员可查敏感字段采购价、馆藏位置。若统一用SELECT * FROM book_info读者也能看到price和location违反安全性要求。解决在DAO层按角色返回不同DTO。读者调用BookPublicDTO仅含title/author/publisher管理员调用BookAdminDTO含全部字段。Spring Security的PreAuthorize结合Query注解实现// 读者端查询自动过滤敏感字段 Query(SELECT new com.example.dto.BookPublicDTO(b.title, b.author, b.publisher) FROM BookInfo b WHERE b.title LIKE %:keyword%) ListBookPublicDTO findPublicBooks(Param(keyword) String keyword); // 管理员端查询全字段 Query(SELECT b FROM BookInfo b WHERE b.title LIKE %:keyword%) ListBookInfo findAdminBooks(Param(keyword) String keyword);4.2 现象顺序图里“借书处理”步骤成功但数据库中book_info.status仍为IN_STOCK原因UML顺序图第12页显示图书借阅管理界面向借阅记录发送消息后再向图书信息发送“更新图书状态”。但开发者常把book_info.update_status(BORROWED)写在BorrowRecord.save()之后若BorrowRecord.save()因唯一键冲突失败book_info状态已改造成数据不一致。解决状态更新必须与借阅记录创建在同一事务内原子执行。正确做法是先book_info.setStatus(BORROWED)内存修改再borrowRecordRepository.save(record)持久化若save()抛异常book_info状态回滚因未commit关键BookInfo实体的setStatus()方法不直接UPDATE DB只改内存值真正的DB更新由JPA在Transactional提交时批量刷入。4.3 现象活动图中“验证是否可借”步骤总返回true但实际应检查读者借阅数是否超限原因UML活动图第12页用菱形框标出“验证是否可借”但未定义验证逻辑。开发者易简单判断book_info.status IN_STOCK忽略ReaderBorrowStatus类定义的max_books规则。解决在Service层注入ReaderBorrowStatusService借书前调用public boolean canBorrow(String readerId) { ReaderBorrowStatus status statusService.getByReaderId(readerId); long currentBorrows borrowRecordRepository.countByReaderIdAndReturnDateIsNull(readerId); return currentBorrows status.getMaxBooks(); }此方法直接读取reader_borrow_status表配置并统计borrow_record中return_date IS NULL的记录数完全复刻UML中“读者借阅状态类”的职责。4.4 现象类图中FineRecord罚款记录类与BorrowRecord借阅记录类双向关联但Hibernate映射时报StackOverflowError原因UML类图第15页显示FineRecord含读者编号、图书编号BorrowRecord也含相同字段开发者为体现关联双向配置ManyToOneOneToMany导致JSON序列化时无限递归罚款记录→借阅记录→罚款记录…。解决BorrowRecord中用JsonIgnore忽略反向引用OneToMany(mappedBy borrowRecord) JsonIgnore // 关键阻止序列化时进入FineRecord private ListFineRecord fineRecords;FineRecord中保留正向引用但DTO层只传必要字段// FineRecordDTO不包含borrowRecord字段只传fineId/amount/status public class FineRecordDTO { private Long fineId; private BigDecimal amount; private String status; }4.5 现象用例描述U2读者信息查询要求“通过借阅证编号查询”但测试时输错编号系统直接500原因UML用例描述第6页写“读者通过系统的用户登录界面输入图书证编号请求查找个人信息”但未规定空值/非法格式处理。开发者直接readerInfoRepository.findById(readerId)ID不存在时JPA抛EmptyResultDataAccessException未被捕获。解决按UML“可选事件流”和“异常事件流”设计兜底GetMapping(/readers/{readerId}) public ResponseEntityReaderInfoDTO getReader(PathVariable String readerId) { try { ReaderInfo reader readerInfoRepository.findById(readerId) .orElseThrow(() - new ReaderNotFoundException(Reader not found: readerId)); return ResponseEntity.ok(reader.toDTO()); } catch (ReaderNotFoundException e) { // 对应UML“可选事件流提示该读者信息不存在” return ResponseEntity.notFound().build(); } }此处ReaderNotFoundException继承RuntimeException由全局异常处理器捕获并返回404严格遵循UML用例U2的“可选事件流”设计。5. 从UML到Spring Boot用LombokMapStructQueryDSL三件套10分钟生成可运行的CRUD骨架5.1 实体层用Lombok消除UML类图的样板代码UML中每个类都列明属性如BookInfo含11个字段手动写Getter/Setter/Constructor极其枯燥。Lombok一键解决// 对应UML类图图书信息类0002 Entity Table(name book_info) Data // 自动生成Getter/Setter/toString/equals/hashCode NoArgsConstructor // 无参构造JPA必需 AllArgsConstructor // 全参构造方便测试 Builder // 构建者模式避免长参数列表 public class BookInfo { Id Column(name book_id) private String bookId; // UML属性图书编号 Column(name isbn, unique true) private String isbn; // UML属性图书ISBN/ISSN号 Column(name title, nullable false) private String title; // UML属性图书名 Column(name author) private String author; // UML属性图书作者 Column(name publisher) private String publisher; // UML属性图书出版社 Column(name price, precision 10, scale 2) private BigDecimal price; // UML属性图书单价 Column(name publish_date) private LocalDate publishDate; // UML属性出版日期 Column(name category) private String category; // UML属性图书分类 Column(name keywords) private String keywords; // UML属性关键词 Column(name copy_count) private Integer copyCount; // UML属性图书副本数 Column(name location) private String location; // UML属性图书所在馆室号 }关键点Data覆盖UML所有属性的访问需求Builder支持链式构建BookInfo.builder().title(算法导论).author(Cormen).build()NoArgsConstructor满足JPA反射实例化要求。比手写200行代码快10倍且零出错。5.2 DTO层用MapStruct精准映射UML的“查询视图”分离UML用例明确区分读者视图U1和管理员视图U5MapStruct自动生成类型安全转换// 定义读者查询DTO对应UML用例U1只暴露必要字段 public class BookPublicDTO { private String bookId; private String title; private String author; private String publisher; private LocalDate publishDate; } // 定义管理员查询DTO对应UML用例U5含全部字段 public class BookAdminDTO extends BookPublicDTO { private BigDecimal price; private String keywords; private Integer copyCount; private String location; } // MapStruct映射器自动生成实现类 Mapper public interface BookMapper { // 读者视图映射过滤敏感字段 Mapping(target price, ignore true) Mapping(target keywords, ignore true) Mapping(target copyCount, ignore true) Mapping(target location, ignore true) BookPublicDTO toPublicDTO(BookInfo bookInfo); // 管理员视图映射全字段 BookAdminDTO toAdminDTO(BookInfo bookInfo); // List批量转换 ListBookPublicDTO toPublicDTOList(ListBookInfo bookInfos); }编译后MapStruct生成BookMapperImpl调用bookMapper.toPublicDTO(bookInfo)即返回精简DTO彻底规避手动new BookPublicDTO()的字段遗漏风险。UML中“不同用户只能访问授权视图”的安全性要求至此在代码层闭环。5.3 查询层用QueryDSL实现UML用例的动态条件拼装UML用例U5图书管理员查询书籍支持“按作者、书名、出版社、出版时间确切/时间段/之前/之后”硬编码SQL极易出错。QueryDSL动态构建// QueryDSL查询对象QBookInfo自动由插件生成 QBookInfo book QBookInfo.bookInfo; // 根据UML用例U5的查询条件动态拼装 BooleanBuilder builder new BooleanBuilder(); if (StringUtils.hasText(keyword)) { builder.and(book.title.contains(keyword).or(book.author.contains(keyword))); } if (StringUtils.hasText(publisher)) { builder.and(book.publisher.eq(publisher)); } if (startDate ! null endDate ! null) { builder.and(book.publishDate.between(startDate, endDate)); // 时间段 } else if (startDate ! null) { builder.and(book.publishDate.goe(startDate)); // 某一时间之后 } else if (endDate ! null) { builder.and(book.publishDate.loe(endDate)); // 某一时间之前 } // 执行查询自动转换为SQL含分页 PageBookInfo result bookInfoRepository.findAll(builder, PageRequest.of(page, size));这段代码将UML用例U5的复杂查询逻辑4种时间条件多字段OR转化为类型安全的Java链式调用。QueryDSL在编译期校验字段名book.title若拼错IDE直接报红避免运行时SQL语法错误。比手写Query注解或Criteria API简洁3倍且完全可测试。5.4 权限层用Spring Security Method Security对接UML参与者权限UML参与者Reader/Librarian/Administrator的权限差异用PreAuthorize精准控制RestController RequestMapping(/api/books) public class BookController { // 对应UML用例U1读者可查但只返回publicDTO GetMapping(/search) PreAuthorize(hasRole(READER)) public ResponseEntityListBookPublicDTO searchPublicBooks( RequestParam String keyword) { return ResponseEntity.ok(bookService.searchPublicBooks(keyword)); } // 对应UML用例U5管理员可查全部字段 GetMapping(/admin/search) PreAuthorize(hasRole(LIBRARIAN)) public ResponseEntityListBookAdminDTO searchAdminBooks( RequestParam String keyword) { return ResponseEntity.ok(bookService.searchAdminBooks(keyword)); } // 对应UML用例U6管理员可修改图书信息 PutMapping(/{bookId}) PreAuthorize(hasRole(LIBRARIAN)) public ResponseEntityBookAdminDTO updateBook( PathVariable String bookId, RequestBody BookUpdateDTO dto) { return ResponseEntity.ok(bookService.updateBook(bookId, dto)); } // 对应UML用例U7管理员可添加图书 PostMapping PreAuthorize(hasRole(LIBRARIAN)) public ResponseEntityBookAdminDTO addBook(RequestBody BookCreateDTO dto) { return ResponseEntity.ok(bookService.addBook(dto)); } }PreAuthorize(hasRole(READER))直接映射UML参与者Reader A1hasRole(LIBRARIAN)对应Librarian A2。当用户token中authorities不含LIBRARIAN时Spring Security自动返回403无需在Service里写if (!user.hasRole(LIBRARIAN)) throw new AccessDeniedException()——这正是UML“安全性要求”中“不同用户只能访问授权视图”的代码级实现。6. 验证UML设计质量的3个硬核技巧用数据库反向工程、JUnit覆盖率、Postman场景链路测试6.1 技巧一用JPA反向工程生成DDL与UML类图做字段级比对别信文档写的“图书信息类含11个属性”用代码生成的SQL才是真相。在Spring Boot中启用spring.jpa.hibernate.ddl-autovalidate开发环境启动时JPA会校验实体与数据库结构一致性。若UML中BookInfo类属性图书摘要abstract在实体里漏写启动直接报错org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing column [abstract] in table [book_info]此时打开target/generated-sources/annotations/下的BookInfo_.javaJPA元模型对比UML类图第14页的属性列表✅book_id,isbn,title,author,publisher,price,publish_date,category,keywords,copy_count,location—— 全部匹配❌abstract摘要未在实体中声明 → 立即补上Column(name abstract) private String abstractText;这招比人眼核对PDF快10倍且100%准确。我每次拿到UML文档第一件事就是跑mvn clean compile看JPA报错5分钟内揪出所有属性遗漏。6.2 技巧二用JUnit 5覆盖UML用例的“基本事件流”和“异常事件流”UML每个用例都定义了“基本事件流”正常路径和“异常事件流”错误路径JUnit测试必须双覆盖。以用例U3图书管理员处理借阅为例SpringBootTest class BorrowServiceTest { Test DisplayName(U3基本事件流读者借书成功) void borrowBookSuccess() { // Given准备读者借阅数0、图书IN_STOCK、状态规则max_books5 ReaderInfo reader createReader(R001); BookInfo book createBook(B001, IN_STOCK); ReaderBorrowStatus status createStatus(STUDENT, 5); // When执行借书 boolean result borrowService.borrowBook(R001, B001); // Then验证U3基本流结果 assertThat(result).isTrue(); // 借阅记录存在 assertThat(borrowRecordRepository.countByReaderIdAndReturnDateIsNull(R001)).isEqualTo(1); // 图书状态变为BORROWED assertThat(bookInfoRepository.findById(B001).get().getStatus()).isEqualTo(BORROWED); } Test DisplayName(U3异常事件流读者借阅超限) void borrowBookExceedLimit() { // Given读者已借5本达max_books上限 ReaderInfo reader createReader(R001); for (int i 0; i 5; i) { borrowRecordRepository.save(createBorrowRecord(R001, B i)); } // When尝试借第6本 Throwable thrown catchThrowable(() - borrowService.borrowBook(R001, B006)); // Then验证U3异常流抛出BorrowLimitExceededException assertThat(thrown) .isInstanceOf(BorrowLimitExceededException.class) .hasMessage(Reader R001 has reached maximum borrow limit: 5); } }每个DisplayName直接引用UML用例IDU3测试用例名即文档索引。覆盖“基本事件流”确保主干逻辑正确“异常事件流”验证容错能力。我坚持每份UML文档至少写20个此类测试覆盖率不到85%不交付——因为UML里写的“异常事件流没有权限...给出错误提示”必须在代码里变成可执行的assertThat(thrown).isInstanceOf(AccessDeniedException.class)。6.3 技巧三用Postman链路测试模拟UML活动图的完整用户旅程UML活动图第12页描述了“借书”从开始到结束的7个步骤Postman可串联成自动化链路步骤Postman请求验证点对应UML活动图节点1POST/api/login读者账号返回JWT token“读者登录”2GET/api/books?keyword算法返回200图书列表“查找图书”3POST/api/borrowbookIdreaderId返回200借阅记录ID“验证是否可借”→“创建借书记录”4GET/api/borrow/records?readerIdR001statusactive返回刚创建的记录“存储借书记录”5GET/api/books/B001status字段为BORROWED“更新图书状态”在Postman中创建Collection用Tests脚本提取token、bookId、recordId并传递给下个请求// 在登录请求的Tests中 const jsonData pm.response.json(); pm.environment.set(token, jsonData.token); // 在借书请求的Tests中 const jsonData pm.response.json(); pm.environment.set(borrowId, jsonData.recordId);运行Collection5个请求自动串起完整借书旅程。若第3步借书返回400立即定位是UML活动图中“验证是否可借”环节失败——比如读者借阅数超限或图书状态非IN_STOCK。这种端本文还有配套的精品资源点击获取