图书采购借阅系统架构说明书:4+1视图与SSH落地
发布时间:2026/9/20 8:14:04 锦皓数字建站

简介这是一份面向图书杂志采购与借阅系统的软件架构设计说明书以单份 Word 文档呈现适合软件工程课程设计、架构入门与项目文档写作参考。文档围绕系统架构展开从简介、架构表示方式、设计目标与约束到用例视图、逻辑视图、进程视图、实施视图等模块逐层说明并涵盖关键功能需求、质量需求、开发策略、接口通信与非功能性需求等内容便于项目经理、程序设计员和测试人员理解系统蓝图与职责边界。压缩包内共 1 个 docx 文件约 367KB体量轻便可直接查阅目录与章节。已有 1439 人学习下载说明其在同类架构文档中具有较高参考热度。读者可借助其中多视图建模思路、架构决策记录与评估方法对照完成需求分析、模块划分和测试框架设计尤其适合需要撰写架构说明书或梳理分层结构的学习者借鉴。1. 图书采购借阅系统的架构说明书为什么比源码更值得先拆接手一个 2010 年前后的 Java Web 项目多数人的第一反应是直奔 src 目录翻代码结果在两三百个文件里迷路还搞不清哪个包该改。图书杂志采购和借阅系统这类项目信息密度最高的地方其实是一份叫《软件架构设计说明书》的 docx它把系统切成用例视图、逻辑视图、进程视图、实施视图、部署视图五份每一份只回答一类涉众的问题项目经理、程序员、测试设计员各取所需互不干扰。这份文档适合三类人看要做毕业设计却不知道架构章节怎么落笔的学生要接手遗留 SSH 项目却看不懂包命名规则的维护者以及需要把稳定、安全、便捷这类形容词翻译成可验收指标的工程师。最该先看的是第三节架构设计目标与约束里的三行数字——查询响应不超过 10 秒其余交互不超过 3 秒平均故障间隔时间不低于 200 小时。这三个数字不是装饰它们直接决定了逻辑视图为什么要把查询单独抽一层、部署视图为什么必须把数据库和 Web 容器拆开。先读这三行再回头看视图整份说明书就顺了。2. 用 41 视图把架构说明书拆成五个可验证的切面2.1 五视图各自的读者和产出物架构文档最容易写崩的地方是把五个视图写成五份互相抄的废话。判断标准很简单每个视图必须对应一个具体的产出物且这个产出物只有这个视图能给。下表是照着这份说明书的章节结构整理出来的对应关系写文档时可以直接当检查表用。视图回答的核心问题主要读者必须产出的内容用例视图系统对外提供哪些可观察行为客户、测试设计员参与者清单、关键用例说明逻辑视图内部由哪些包和子系统组成程序设计员层次模型、设计包清单进程视图运行时刻进程与线程如何交互程序设计员、性能测试角色进程消息序列实施视图代码如何映射到构件与配置管理配置管理员实施模型、模块归属部署视图软件构件跑在哪些物理节点上运维、项目经理节点拓扑、部署方案关键点在于层次用例视图决定逻辑视图的包划分逻辑视图决定进程视图里哪些调用要跨进程进程视图和部署视图共同决定质量指标能不能达标。顺序颠倒过来写文档基本就要返工。2.2 用例视图里的五类参与者与权限边界这份说明书把参与者分成游客、读者、图书管理员、采购管理员、系统管理员五类。抽用例时不要对着需求文档逐条抄按下面的顺序走一遍能省掉大量返工圈出需求文档里所有动宾短语借书、还书、续借、罚款、订购、取消订购、验收确定、编目入库、预约。按操作对象归类同一对象的动词合并成一个用例比如续借和还书都归到流通管理。标出每个用例的主参与者与触发条件借书的前置条件是证件有效且无超期。反向检查是否存在某个角色能覆盖全部用例。系统管理员就是典型例外他只管管理员账号不碰图书业务。判断完边界落到数据库就是一张角色权限关系。早期项目常见做法是直接在用户表里放一个 role 字段结果加一个采编人员就要改代码。-- 把用例视图里的五类参与者落成三张表避免角色硬编码在 Java 里 CREATE TABLE sys_role ( role_id INT PRIMARY KEY AUTO_INCREMENT, role_code VARCHAR(32) NOT NULL UNIQUE COMMENT GUEST/READER/LIB_ADMIN/PUR_ADMIN/SYS_ADMIN, role_name VARCHAR(64) NOT NULL ); CREATE TABLE sys_permission ( perm_id INT PRIMARY KEY AUTO_INCREMENT, perm_code VARCHAR(64) NOT NULL UNIQUE COMMENT 如 book.query / loan.renew / purchase.instore ); CREATE TABLE role_permission ( role_id INT NOT NULL, perm_id INT NOT NULL, PRIMARY KEY (role_id, perm_id) ); -- 借阅记录按读者加状态建联合索引直接服务“查看借阅/归还信息”的 3 秒约束 CREATE INDEX idx_loan_reader_status ON loan_record (reader_id, loan_status);三张表的分工是sys_role存角色定义sys_permission存原子权限role_permission做多对多关联。perm_code用点号分段前缀对应模块方便在拦截器里做通配匹配。联合索引的顺序不能反reader_id在前是因为查询一定带读者身份loan_status在后做范围过滤反过来建索引就只能用到第一列。2.3 从关键质量需求倒推架构约束质量需求如果不翻译成技术动作就等于没写。查询不超过 10 秒这条在图书表几万条数据时其实很容易达标但借阅排行榜、新书通报、藏书多条件查询是三条不同的 SQL任何一条走了全表扫描都会拖垮整体。对应动作是给查询类接口单独走一个只读数据源并在book表的title、isbn、category_id上按实际查询组合建索引。其他交互不超过 3 秒约束的是写操作之外的所有请求包括登录、注册、改个人信息。这类请求的共同点是单表操作加会话校验慢下来的原因通常是登录后每次请求都重新查一遍权限。缓解方式是登录时把权限码集合放进会话拦截器只读会话不查库。MTBF 不低于 200 小时则决定了部署视图不能把应用和数据库塞在同一台机器。200 小时约等于八天多一个教学规模的项目平均八天出一次故障说明单点故障必须被隔离数据库进程崩溃不能把 Web 容器一起带走。提示这三条指标写在文档里时要带上测量口径比如查询响应时间指服务端处理时间不含网络传输否则验收时双方各说各话。3. 逻辑视图落地StrutsSpringHibernate 的包结构与装配配置3.1 四个基础包的职责切分说明书里给出了bpms.action、bpms.actionForm、bpms.db、bpms.domain四个基础包这个划分是 SSH 时代最典型的分层法但只给包名不给依赖规则代码照样写乱。补上依赖方向之后才是可执行的约束。包名放什么允许依赖一旦出现就说明分层破了bpms.actionStruts Action参数接收与页面跳转actionForm、service直接写 JDBC 或 HQLbpms.actionForm表单对象纯数据载体无任何业务判断bpms.domain实体类与 Hibernate 映射无依赖 action 或 dbbpms.dbDAO 与 Hibernate 会话操作domain页面跳转、request 取值依赖只能单向action 依赖 serviceservice 依赖 dbdb 依赖 domain。反向依赖一次都不要开尤其是让 domain 引用 action编译期看着没事打包时会带出整个 Web 层。3.2 Struts 与 Spring 的装配配置Struts 负责路由和表单绑定Spring 负责对象生命周期和事务两者靠DelegatingActionProxy接起来。这个代理类是整套配置的关键它让 Struts 不去 new Action而是按path去 Spring 容器里找同名 bean。!-- struts-config.xml只做路由业务全部下沉到 Service -- struts-config form-beans form-bean namebookSearchForm typebpms.actionForm.BookSearchForm/ /form-beans action-mappings action path/bookSearch typeorg.springframework.web.struts.DelegatingActionProxy namebookSearchForm scoperequest validatefalse forward namelist path/WEB-INF/jsp/book/list.jsp/ /action /action-mappings /struts-config!-- applicationContext.xmlbean 名必须与 struts 的 path 一致否则启动即报找不到 Action -- bean name/bookSearch classbpms.action.BookSearchAction property namebookService refbookService/ /bean bean idbookService classbpms.service.impl.BookServiceImpl property namebookDao refbookDao/ /bean !-- 声明式事务按方法名前缀决定传播行为和只读标记 -- bean idtxProxyTemplate abstracttrue classorg.springframework.transaction.interceptor.TransactionProxyFactoryBean property nametransactionManager reftransactionManager/ property nametransactionAttributes props prop keyquery*PROPAGATION_REQUIRED,readOnly/prop prop keysave*PROPAGATION_REQUIRED/prop prop key*PROPAGATION_REQUIRED/prop /props /property /beanscoperequest表示表单对象每个请求新建避免上一个用户的数据残留到下一个用户validatefalse是关掉 Struts 自带的校验框架改由 Spring 侧的校验器统一处理两套校验同时开着最容易出现页面提示通过、后台报错的诡异现象。事务属性里query*加readOnly让 Hibernate 跳过脏检查查询类接口能省下可观的 CPU。PROPAGATION_REQUIRED表示有事务就加入、没有就新建写操作必须用这个改成SUPPORTS会导致部分写操作没有事务包裹。3.3 Hibernate 映射与事务边界实体映射别偷懒全用注解book与category、loan_record与reader这几组关系在 hbm.xml 里显式写出抓取策略更可控。默认的懒加载在页面渲染时才触发查询一旦 Action 里提前把 Session 关了就是经典的LazyInitializationException。// BookDaoImpl查询方法不手动开事务交给 Spring 代理 public class BookDaoImpl extends HibernateDaoSupport implements BookDao { public ListBook queryByCondition(BookSearchForm form, int pageNo, int pageSize) { // 用 HQL 拼条件禁止把表单字段直接字符串拼接进 SQL StringBuilder hql new StringBuilder(from Book b where 11 ); MapString, Object params new HashMapString, Object(); if (StringUtils.hasText(form.getTitle())) { hql.append(and b.title like :title ); params.put(title, % form.getTitle().trim() %); } if (form.getCategoryId() ! null) { hql.append(and b.categoryId :categoryId ); params.put(categoryId, form.getCategoryId()); } hql.append(order by b.createTime desc); Query query getSession().createQuery(hql.toString()); for (Map.EntryString, Object e : params.entrySet()) { query.setParameter(e.getKey(), e.getValue()); } query.setFirstResult((pageNo - 1) * pageSize); // 分页起点从 0 开始 query.setMaxResults(pageSize); // 每页条数建议固定 20 return query.list(); } }setFirstResult的算法是(页码 - 1) × 每页条数页码从 1 开始传别从 0 开始否则第一页会变成空。like条件用参数占位符而不是拼接既防注入也让 Hibernate 能复用执行计划。分页条数固定成常量而不是让页面传是为了防止有人传 10000 把库拖死。3.4 分层落地常踩的三个坑第一个是 N1 查询。列表页显示每本书的类别名如果类别是懒加载20 本书就是 21 条 SQL。解法是在 hbm.xml 里对该关联加fetchjoin或在 HQL 里写left join fetch。第二个是 ActionForm 跨请求复用。把 scope 设成 session 之后用户 A 的查询条件会出现在用户 B 的页面上这是权限事故的高发点。第三个是事务自调用失效。Service 内部用this.saveXxx()调自己的另一个方法Spring 的 AOP 代理拦不到事务配置直接作废。要么抽成独立的 DAO 方法要么注入自身代理。4. 进程视图走通三条主链路查询、注册与采购入库的调用时序4.1 搜索图书链路的六步消息说明书里画的搜索链路是主界面向后台发请求、后台取数据、数据库返回、再层层回传一共六步。落到代码上这六步压缩成一次 Action 调用加一次 Service 查询真正需要关注的是每一步失败时回传什么。// BookSearchAction接收分页参数异常统一转成状态信息返回页面 public class BookSearchAction extends DispatchAction { private BookService bookService; public ActionForward query(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { BookSearchForm searchForm (BookSearchForm) form; int pageNo searchForm.getPageNo() null ? 1 : searchForm.getPageNo(); try { PageResultBook page bookService.queryByCondition(searchForm, pageNo, 20); request.setAttribute(page, page); request.setAttribute(status, SUCCESS); return mapping.findForward(list); } catch (Exception e) { // 不把堆栈抛给页面只回一个状态码详细日志落 log4j log.error(book search failed, form searchForm, e); request.setAttribute(status, QUERY_ERROR); return mapping.findForward(list); } } public void setBookService(BookService bookService) { this.bookService bookService; } }pageNo为空时兜底成 1防止第一页就报空指针。异常不往上抛而是转成QUERY_ERROR状态码对应进程视图里那条状态信息成功与否的回传消息页面拿到状态码只显示一句提示不暴露数据库结构。日志里带上完整表单对象排查时不用再问用户输入了什么。分页参数怎么定直接影响 10 秒那条指标。经验值如下表。参数建议值说明pageSize20列表页可视范围超过 50 单页渲染会明显变慢最大页深500深度分页改用按主键游标setFirstResult到几万会全表扫查询超时8 秒留在 10 秒指标之内超时直接中断并回状态码缓存范围排行榜类新书推荐、借阅排行按小时缓存藏书查询不缓存4.2 注册与改资料的状态回传为什么要统一游客注册和读者修改个人信息在进程视图里是两条几乎一样的消息序列填表、提交、写库、回传状态。这两个用例的差异只在目标实体和权限校验所以状态码必须共用一套枚举否则页面要写两套判断逻辑。// 统一状态码注册、改资料、采购入库共用页面只需处理这几种 public enum ResultCode { SUCCESS, // 操作成功 PARAM_INVALID, // 必填项缺失或格式错误 DUPLICATE, // 账号、ISBN 等唯一键冲突 NO_PERMISSION, // 会话失效或角色不符 SYSTEM_ERROR // 数据库或未知异常 }DUPLICATE单独拎出来是因为账号重复和 ISBN 重复在页面上要给出不同的跳转引导。NO_PERMISSION和SYSTEM_ERROR分开前者回登录页后者只刷新当前页并提示稍后重试混在一起用户会被莫名踢下线。4.3 采购入库的状态流转采购管理模块是这份说明书里状态最多的一条链路图书订购、取消订购、验收确定、编目入库捐赠和交换来的图书要先清点确认再编目。状态不能只用一个字段表示否则分不清已验收但未编目和已编目但未上架。当前状态触发动作数据变更是否可回退订购中取消订购状态置为已取消释放预算占用可回退订购中验收确定数量、单价、实收册数写入明细不可已验收编目入库生成图书主记录分配条码与分类号不可已入库清点确认捐赠/交换图书补录来源字段不可修补中重新上架状态回可借同时解除流通冻结可回退修补中这个状态容易被漏掉它属于流通管理而不是采购但会直接影响借书权限判断——处于修补中的图书即使库存字段显示有货也必须拒绝借出。4.4 借书权限校验的判定顺序说明书明确列出了借书时要自动区别的因素超期、未交罚款、证件有效期、预约、其他违规。这五项的判定顺序不能随意调因为它们的处理动作不同有的要跳罚款页有的只是拒绝。// 借书前置校验短路返回顺序即优先级 public ResultCode checkBorrowPermission(Reader reader, Book book) { if (reader null || !reader.isCardValid()) { return ResultCode.NO_PERMISSION; // 证件过期最先判后续查询无意义 } if (reader.getUnpaidFine() ! null reader.getUnpaidFine() 0) { return ResultCode.NO_PERMISSION; // 有未缴罚款直接拒 } if (reader.getOverdueCount() 0) { return ResultCode.NO_PERMISSION; // 有超期未还先还书再借 } if (book.getStatus() BookStatus.REPAIRING) { return ResultCode.PARAM_INVALID; // 修补中页面提示改借其他书 } if (book.getAvailableCopies() 0) { return ResultCode.DUPLICATE; // 无可借副本引导走预约 } return ResultCode.SUCCESS; }证件有效性排第一因为证件过期时读者身份本身就不成立后面几项查了也是白查。罚款和超期都返回NO_PERMISSION但页面提示词不同实现时可以在 ResultCode 外再挂一个 message key。可借副本为零时返回DUPLICATE并引导预约而不是简单拒绝这条对应说明书里预约处理这个用例。5. 质量约束转验收项3 秒响应与 200 小时 MTBF 怎么测文档写完不算完架构说明书里的指标要能被复测才有意义。三个数字里最容易糊弄的是响应时间最常见的做法是用ab或 JMeter 打一轮然后拿平均值交差。平均值会掩盖长尾验收要看的是 95% 分位。# 对查询接口压测-n 总请求数-c 并发数输出里直接看 95% 那行 ab -n 2000 -c 50 http://localhost:8080/bpms/bookSearch.do?methodquerypageNo1 # 只读接口连打三轮取最差的一轮作为验收数据避免缓存命中造成假象 for i in 1 2 3; do ab -n 1000 -c 30 http://localhost:8080/bpms/bookSearch.do?methodquerypageNo1 \ | grep -E 95%|Failed done-c 50是并发连接数压测值参考的是同时在线阅读的用户规模不是注册用户总数。三轮取最差是因为第一轮往往命中了 Hibernate 二级缓存数据虚高。Failed requests只要不为零就要先查原因连接超时和业务异常在ab的输出里是混在一起的。10 秒那条指标还要配合慢查询日志验证否则压测时数据量太小上线必炸。-- 开慢查询阈值定 2 秒留出到 10 秒之间的排查余量 SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 2; -- 对藏书多条件查询做执行计划检查重点看 type 和 rows 两列 EXPLAIN SELECT b.book_id, b.title, b.author FROM book b WHERE b.category_id 12 AND b.title LIKE 软件% ORDER BY b.create_time DESC LIMIT 20;type出现ALL就是全表扫描rows超过五位数基本要加索引。title上的前置模糊匹配用不到索引所以这类查询要么限定分类先缩小范围要么依赖单独的搜索引擎别指望在 MySQL 里硬扛。MTBF 不低于 200 小时属于运行期指标测法是采日志里的启动时间戳算两次启动之间的间隔。# 从日志中提取应用启动时间计算相邻两次启动的间隔单位小时 grep Application startup complete app.log \ | awk {print $1 $2} \ | awk NR1 {cmddate -d \$0\ %s; cmd | getline t; close(cmd); print (t-prev)/3600 小时; prevt} NR1 {cmddate -d \$0\ %s; cmd | getline t; close(cmd); prevt}间隔时长小于 200 的记录都要单独看一次堆栈常见原因是连接池耗尽和OutOfMemoryError。连接池上限如果按默认的 8 配并发 50 时就会大面积等待超时这个值和压测并发数要一起定。文档本身的交付也有坑。这类架构说明书通常以 docx 形式流转用 WPS 打开时目录域和交叉引用容易错位建议定稿后另存一份 PDF 作为评审版本docx 只留作可编辑源文件。图表编号改成手动维护的固定文本别依赖自动编号否则任何一次增删章节都会让引用全部错位。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。