软件设计说明书模板:MVC分层+事务契约+状态机的工程化实践

发布时间:2026/10/27 10:40:14
软件设计说明书模板:MVC分层+事务契约+状态机的工程化实践

简介:本资源是一份面向软件开发工程师、系统架构师及高校计算机专业学生的《软件设计说明书》标准化模板与完整案例,解决实际项目中设计文档编写不规范、内容缺失、结构混乱等常见问题。文档基于UML建模工具Rose构建,覆盖从总体架构到模块级实现的全维度设计要素,包含简介、需求描述、两层结构设计(系统级+模块级)、数据库设计(含可选详细设计)、组件视图、进程视图及模块详细设计等9大核心章节,并附带修订记录、密级标识、缩略语清单与规范目录,具备企业级文档交付标准。资源为1个2.96MB的Word(.doc)文件,结构完整、排版严谨,可直接套用或教学示范。目前已有1740人学习下载,适合初学者掌握设计文档框架,也适用于中级工程师快速产出合规设计输出,是软件工程实践与CMMI过程改进的重要参考材料。

1. 这份软件设计说明书模板,不是填空题,而是你答辩时能直接摊开讲的“设计黑匣子”

很多同学交毕设/课设时卡在「写完文档但自己都说不清设计逻辑」——明明代码跑通了,一被问「为什么用Spring不用MyBatis原生?」「DAO层为什么要拆成Impl和接口?」「用户管理模块的异常流怎么兜底?」就卡壳。这不是能力问题,是文档没把「设计决策」真正落地成可追溯、可复现、可答辩的结构化表达。这份《软件设计说明书模板及案例说明》不是套话堆砌的Word格式范本,而是一份从某高校物流教学系统(EYP仓储管理系统V100R001C01B010)真实交付中反向提炼出的带血泪经验的设计说明书骨架:它用21页纸完整覆盖了从MVC分层依据、Tapestry+Spring+Hibernate三件套的耦合点控制、到UserManagerImpl类里每个方法的输入校验边界和事务传播行为。它解决的不是「要不要写数据库设计」这种伪命题,而是「当评审老师指着第18页的类图问‘这个UserDaoImpl的save()方法为什么抛DataAccessException而不是自定义业务异常’时,你能立刻翻到第19页的‘8.1.5 Methods’表格,指着‘Exception’栏说清楚这是为适配Spring AOP统一异常拦截器预留的契约」。适合正在赶毕设/课设、需要一份能过导师初审、能撑住答辩追问、能直接改成本专业系统文档的同学,也适合刚入职的工程师快速建立「设计即契约」的交付意识。

2. 总体架构设计:三层结构与四个核心框架的职责切分

2.1 架构选型依据:为什么是 Tapestry+Spring+Hibernate 而非 Spring Boot 全家桶?

在EYP项目立项阶段,团队对比了Spring Boot单体架构与Tapestry+Spring+Hibernate组合方案。最终选择后者,核心依据有三点:
第一,教学场景强约束:系统需作为物流管理课程的教学演示平台,要求界面层(Page)与业务逻辑(Service)完全解耦,便于教师替换不同实现(如用MockService模拟高并发库存扣减)。Tapestry的组件化Page模型天然支持「一个HTML模板绑定多个Java Page类」,而Spring Boot的Thymeleaf模板与Controller强绑定,修改页面逻辑需同步改Controller签名,教学演示时易出错。
第二,ORM透明性需求:学生需观察Pojo→Vo→数据库表的映射全过程。Hibernate的@Entity注解+hbm.xml双模式,允许在User.java中用@Id @GeneratedValue(strategy = GenerationType.IDENTITY)声明主键,同时在User.hbm.xml中显式配置<id name="id" column="user_id">,这种「代码+配置」双轨制比JPA注解单轨更利于教学拆解。
第三,AOP织入粒度可控:权限校验需在Service层方法入口做@PreAuthorize("hasRole('TEACHER')"),但日志记录需在DAO层执行SQL后捕获耗时。Spring原生AOP对@Repository类代理支持弱,而Hibernate的Interceptor接口可精准拦截onSave()、onLoad()事件,配合Spring的TransactionSynchronizationManager,实现「事务提交后才写审计日志」的强一致性。

提示:若你的课设/毕设面向教学或需展示技术原理,慎用Spring Boot自动装配。它省掉的配置,恰恰是答辩时最该讲清的设计权衡。

2.2 系统分层结构:从用户请求到数据库落盘的七步链路

EYP系统的请求处理链路严格遵循MVC分层,但每层职责有明确契约,避免常见「Service层写SQL」「Controller层做权限判断」的越界操作:

  1. Client(IE浏览器):发送HTTP GET/POST请求,URL路径含模块标识(如/user/list.action),参数经UTF-8编码
  2. Tapestry Page层:UserPage.java继承BasePage,onActionFromSearchButton()方法接收请求,调用usersManager.searchUsers(keyword)
  3. Service层:UsersManagerImpl.java实现UsersManager接口,searchUsers()方法内先校验keyword.length() <= 50,再调用userDao.findByName(keyword)
  4. DAO层:UserDaoImpl.java继承HibernateBaseDaoImpl<User>,findByName()方法执行session.createQuery("from User u where u.name like :name")
  5. Hibernate ORM层:将HQL转为SQL,通过Connection执行SELECT * FROM t_user WHERE name LIKE ?
  6. Database层:MySQL返回结果集,Hibernate将每行映射为User对象(Pojo)
  7. Page层回传:UserPage.java将List<User>放入@Inject Container,Tapestry自动渲染UserPage.html中的<t:loop source="users">

该链路在文档第11页「3.2.1 业务流程说明」中以文字+箭头图呈现,关键在于每步的输入输出契约被明确定义:

  • UserPage.onActionFromSearchButton()输入:无参数(从@Inject Component获取表单值),输出:PageRender对象
  • UsersManagerImpl.searchUsers()输入:String keyword,输出:List<User>,异常:IllegalArgumentException(keyword超长)
  • UserDaoImpl.findByName()输入:String name,输出:List<User>,异常:DataAccessException(数据库连接失败)

这种契约化描述,让评审老师能快速定位「某环节该谁负责」,避免责任模糊。

2.3 四大框架协同机制:依赖注入与事务边界的硬性约定

Tapestry、Spring、Hibernate、ACEGI(安全框架)的集成不是简单引入jar包,而是通过三处硬性约定保障稳定性:

约定一:依赖注入容器分层

  • Tapestry管理Page层对象(UserPage),通过@Inject注入UsersManager
  • Spring管理Service/DAO层对象(UsersManagerImpl、UserDaoImpl),通过@Autowired注入UserDao
  • Hibernate由Spring托管SessionFactory,UserDaoImpl通过@Resource SessionFactory sessionFactory获取会话
  • ACEGI的FilterSecurityInterceptor由Spring配置,拦截所有/user/**请求

约定二:事务边界仅在Service层开启
UsersManagerImpl.java中所有业务方法均标注@Transactional:

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void updateUser(User user) { // 1. 校验业务规则(如邮箱格式) if (!user.getEmail().matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")) { throw new BusinessException("邮箱格式错误"); } // 2. 执行DAO操作 userDao.update(user); // 此处抛异常会触发事务回滚 // 3. 记录操作日志(非数据库操作,不参与事务) logService.log("USER_UPDATE", user.getId()); }

关键点:logService.log()调用放在userDao.update()之后,确保日志只在事务成功提交后写入,避免「数据库回滚但日志已存」的不一致。

约定三:ACEGI权限校验前置到Service入口
不采用Spring Security的URL级拦截(如/user/**=ROLE_TEACHER),而是在UsersManagerImpl方法上加注解:

@PreAuthorize("hasRole('TEACHER')") public List<User> getAllUsers() { ... }

原因:教学系统需支持「同一URL,教师看全部用户,学生只看本人信息」,URL拦截无法区分角色内细粒度权限,必须由Service方法根据SecurityContextHolder.getContext().getAuthentication().getAuthorities()动态判断。

3. 模块详细设计:用户管理模块的类图、方法契约与状态流转

3.1 用户管理模块结构:从Page到DAO的六层对象映射

EYP用户管理模块的类结构严格遵循「表现层-控制层-服务层-数据访问层-实体层-视图层」六层映射,文档第13页图示清晰展示了依赖关系:

  • UserPage.java(Tapestry Page) →@Inject UsersManager
  • UsersManagerImpl.java(Spring Service) →@Autowired UserDao
  • UserDaoImpl.java(Spring DAO) →@Resource SessionFactory
  • User.java(Hibernate Entity/Pojo) →@Entity @Table(name="t_user")
  • UserVO.java(View Object) →public class UserVO { private Long id; private String name; }
  • UserPage.html(Tapestry模板) →<t:loop source="users" value="user"> <t:output value="user.name"/> </t:loop>

关键设计点:VO与Pojo分离。User.java含@Id、@Column等ORM注解,UserVO.java仅为纯数据载体,避免前端模板直接引用ORM实体导致LazyInitializationException(因Tapestry渲染时Session已关闭)。UsersManagerImpl中转换逻辑如下:

// 将Pojo转VO,供Page层使用 public List<UserVO> convertToVO(List<User> users) { List<UserVO> vos = new ArrayList<>(); for (User user : users) { UserVO vo = new UserVO(); vo.setId(user.getId()); vo.setName(user.getName()); vo.setEmail(user.getEmail()); vos.add(vo); } return vos; }

3.2 UserManagerImpl核心方法契约:输入校验、异常分类与事务行为

文档第18页「8.1.5 Methods」表格对updateUser()方法的定义,是答辩时最常被深挖的细节。其完整契约如下:

字段值说明
Method NameupdateUser方法名,与接口UsersManager保持一致
Prototypepublic void updateUser(User user)可见性为public,参数为User类型,无返回值
Description更新用户基本信息,包括姓名、邮箱、角色功能描述需精确到字段级,不写「维护用户数据」等模糊表述
CallsuserDao.update(user),logService.log(...)明确列出调用的下层方法,logService不参与事务
Called ByUserPage.onActionFromUpdateButton()上层调用方,证明Page层不包含业务逻辑
Inputuser.id:非空Long,user.email:非空String且符合邮箱正则输入参数校验规则,user.id为空时抛IllegalArgumentException
Output无方法无返回值,状态变更通过数据库体现
Returnvoid无返回值
ExceptionBusinessException(业务规则违反)、DataAccessException(数据库异常)异常分类明确,BusinessException用于邮箱格式错误等可恢复错误,DataAccessException用于连接池耗尽等系统级错误

注意:updateUser()方法内不处理user.password的加密。密码加密由UserPage.java在调用前完成:user.setPassword(BCrypt.hashpw(newPassword, BCrypt.gensalt()))。这是为保证「密码永远不以明文形式进入Service层」的安全契约。

3.3 用户状态机设计:从创建到禁用的四态流转

用户实体User.java虽无显式状态字段,但通过status字段(TINYINT类型)实现四态机,文档第18页「8.1.3 状态设计」用状态图描述其流转规则:

  • INIT(0):用户注册后初始状态,email_verified=false,不可登录
  • ACTIVE(1):邮箱验证通过后置为ACTIVE,可正常登录
  • LOCKED(2):连续5次密码错误后锁定24小时,locked_until=now()+86400
  • DISABLED(3):管理员手动禁用,disabled_by=admin_id,disabled_at=now()

关键约束:

  • 状态只能单向升级(INIT→ACTIVE→LOCKED→DISABLED),禁止降级(如ACTIVE不能直接变DISABLED,需经LOCKED)
  • LOCKED状态自动解锁:DAO层UserDaoImpl的checkLogin()方法中,查询前先执行UPDATE t_user SET status=1 WHERE status=2 AND locked_until < NOW()
  • DISABLED状态不可逆:UsersManagerImpl.enableUser()方法仅对status=2(LOCKED)生效,对status=3(DISABLED)抛IllegalStateException

此状态机设计在答辩中可直观展示「业务规则如何固化到代码」,比口头描述更有说服力。

4. 数据库设计:实体关系、存储过程与完整性约束的落地细节

4.1 用户表(t_user)核心字段设计与约束说明

文档第15页「5.1.1 实体定义」对t_user表的定义,远超基础CRUD字段,体现教学系统对数据治理的要求:

字段名类型NullKeyDefaultExtra说明
user_idBIGINTNOPRINULLauto_increment主键,自增,避免UUID的索引碎片
usernameVARCHAR(50)NOUNINULL唯一索引,长度50满足中文名+英文名
passwordVARCHAR(100)NONULLBCrypt加密后长度≤100,VARCHAR而非TEXT提升查询性能
emailVARCHAR(100)NOUNINULL唯一索引,支持xxx@xxx.edu.cn教育邮箱
statusTINYINTNO0四态机字段,0=INIT,1=ACTIVE,2=LOCKED,3=DISABLED
locked_untilDATETIMEYESNULLstatus=2时必填,status≠2时为NULL,用CHECK约束
created_atDATETIMENOCURRENT_TIMESTAMP创建时间,自动填充
updated_atDATETIMENOCURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP更新时间,自动更新

关键约束实现:

  • locked_until的NULL约束通过MySQLCHECK实现:
ALTER TABLE t_user ADD CONSTRAINT chk_locked_until CHECK ((status = 2 AND locked_until IS NOT NULL) OR (status != 2 AND locked_until IS NULL));
  • email唯一性通过UNIQUE KEY uk_email (email)索引保证,而非应用层校验,避免并发插入冲突。

提示:答辩时若被问「为什么用TINYINT存状态而非ENUM?」,可答:「ENUM在MySQL中修改枚举值需ALTER TABLE锁表,教学系统需支持运行时动态扩展状态(如增加PENDING_APPROVAL),TINYINT配合字典表更灵活」。

4.2 存储过程sp_update_user_status:事务内状态变更与日志联动

文档第19页「9.1 存储过程#」定义的sp_update_user_status,是体现「数据库层强一致性」的关键设计。其功能为:原子化更新用户状态,并在t_operation_log表记录操作。完整实现如下:

DELIMITER $$ CREATE PROCEDURE sp_update_user_status( IN p_user_id BIGINT, IN p_new_status TINYINT, IN p_operator_id BIGINT, OUT p_result_code INT, OUT p_result_msg VARCHAR(255) ) BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; SET p_result_code = -1; SET p_result_msg = '数据库操作失败'; END; START TRANSACTION; -- 1. 校验用户是否存在 IF NOT EXISTS (SELECT 1 FROM t_user WHERE user_id = p_user_id) THEN SET p_result_code = 101; SET p_result_msg = '用户不存在'; ROLLBACK; LEAVE proc_label; END IF; -- 2. 校验状态变更合法性(禁止INIT→DISABLED) IF (SELECT status FROM t_user WHERE user_id = p_user_id) = 0 AND p_new_status = 3 THEN SET p_result_code = 102; SET p_result_msg = '禁止从初始状态直接禁用'; ROLLBACK; LEAVE proc_label; END IF; -- 3. 更新用户状态 UPDATE t_user SET status = p_new_status, updated_at = NOW(), locked_until = CASE WHEN p_new_status = 2 THEN DATE_ADD(NOW(), INTERVAL 24 HOUR) ELSE NULL END WHERE user_id = p_user_id; -- 4. 记录操作日志 INSERT INTO t_operation_log (operator_id, target_type, target_id, action, created_at) VALUES (p_operator_id, 'USER', p_user_id, CONCAT('STATUS_UPDATE_TO_', p_new_status), NOW()); COMMIT; SET p_result_code = 0; SET p_result_msg = '操作成功'; proc_label: BEGIN END; END$$ DELIMITER ;

设计要点说明:

  • 事务完整性:START TRANSACTION包裹所有操作,EXIT HANDLER确保异常时回滚,避免「状态更新成功但日志未写入」
  • 业务规则嵌入:IF语句校验状态变更合法性,将INIT→DISABLED的非法路径在DB层拦截,而非依赖应用层
  • 输出参数契约:p_result_code返回码(0=成功,101=用户不存在,102=非法状态变更),p_result_msg为对应提示,Service层可直接映射为BusinessException

此存储过程在UsersManagerImpl.java中通过jdbcTemplate.call()调用,证明「复杂业务规则应下沉至数据库」的设计思想。

4.3 外键与索引策略:查询性能与数据一致性的平衡

t_user表关联t_role(角色表)、t_operation_log(日志表),外键设计体现教学系统对数据一致性的重视:

  • t_user.role_id→t_role.role_id:ON DELETE RESTRICT,禁止删除被用户引用的角色,避免「用户有角色但角色表无记录」的脏数据
  • t_operation_log.operator_id→t_user.user_id:ON DELETE SET NULL,当操作员账号被禁用时,日志仍保留,仅operator_id置为NULL

索引优化:

  • t_user表建复合索引idx_status_updated:(status, updated_at),支撑「查询最近10个被禁用用户」的高频查询:
SELECT * FROM t_user WHERE status = 3 ORDER BY updated_at DESC LIMIT 10;
  • t_operation_log表建索引idx_target_type_target_id:(target_type, target_id),支撑「查询某用户所有操作日志」:
SELECT * FROM t_operation_log WHERE target_type = 'USER' AND target_id = 123;

索引选择基于EYP系统实际监控数据:status字段区分度低(仅4个值),但与updated_at组合后,WHERE status=? ORDER BY updated_at DESC查询响应时间从1.2s降至0.03s。

5. 常见问题排查:写设计说明书时最容易翻车的五个坑

5.1 现象:评审老师指出「类图里UserPage依赖UsersManager,但文档没写清楚依赖注入方式」

原因:设计说明书混淆了「逻辑依赖」与「物理实现」。UML类图用虚线箭头表示依赖,但未说明该依赖是通过Tapestry的@Inject还是Spring的@Autowired注入,导致实现时出现NullPointerException。

解决:在「3.3.1 Tapestry描述」章节中,必须明确写出:

「UserPage类通过Tapestry框架的@Inject注解注入UsersManager实例,注入发生在Tapestry IoC容器初始化UserPage对象时,UsersManager实例由Spring容器托管,Tapestry通过SpringBeanLocator桥接器获取。」
同时在类图下方添加注释框:「依赖注入方式:Tapestry@Inject+ Spring Bridge」。

5.2 现象:答辩时被问「DAO层方法为何抛DataAccessException而非自定义异常?」

原因:未在「8.1.5 Methods」表格的Exception栏说明异常分类依据。DataAccessException是Spring JDBC/Hibernate的顶层异常,其子类(如HibernateException、JdbcSQLException)已涵盖所有数据库异常场景,自定义异常会增加不必要的包装层级。

解决:在Exception栏补充说明:

「抛出DataAccessException及其子类,因Spring事务管理器(DataSourceTransactionManager)仅识别该异常体系进行回滚。若自定义DbException,需在@Transactional(rollbackFor = DbException.class)中显式声明,违背『异常体系与框架对齐』原则。」
并附上Spring官方文档链接(可写为「参见Spring Framework 5.3.x Reference, Chapter 13. Transaction Management」)。

5.3 现象:数据库设计部分被批「只有ER图,没有说明索引策略和查询场景」

原因:设计说明书将「数据库设计」等同于「画ER图」,忽略索引对教学系统查询性能的影响。EYP系统需支持「教师导出全量用户列表」,若t_user表无索引,SELECT * FROM t_user在10万数据量下超时。

解决:在「5.1.1 实体定义」表格后,新增「索引设计」子节:

索引名字段类型适用查询场景
uk_usernameusernameUNIQUE用户登录时根据用户名查用户
idx_status_updatedstatus, updated_atNORMAL查询某状态(如DISABLED)下最新更新的用户
idx_emailemailUNIQUE邮箱唯一性校验与找回密码
并注明:「所有索引均经EXPLAIN验证,type=ref或type=range,避免type=all全表扫描」。

5.4 现象:模块详细设计中「方法描述」写成伪代码,但缺少输入校验边界

原因:伪代码只描述主流程(如if user.email is valid then save),未定义valid的具体规则(如邮箱长度≤100、必须含@符号、域名部分≤253字符),导致测试用例无法覆盖边界值。

解决:在「8.1.5 Methods」的Input栏,用正则表达式+长度约束明确定义:

「user.email:非空字符串,匹配正则^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$,总长度≤100字符。校验在UsersManagerImpl.updateUser()方法入口执行,不满足则抛IllegalArgumentException("邮箱格式错误")。」
同时在文档附录提供测试用例表:testEmailValid_abc@def.edu.cn(通过)、testEmailInvalid_abc@(失败)。

5.5 现象:Revision Record(修订记录)日期格式混乱,被质疑文档管理不规范

原因:修订记录中混用2001-06-08、06/08/2001、2001年6月8日等格式,违反CMMI V3.0「文档元数据标准化」要求。

解决:在文档第2页「Revision Record」表格上方,添加强制规范:

「所有日期字段必须采用ISO 8601格式:YYYY-MM-DD(如2023-10-15),禁止使用斜杠/、中文年/月/日、英文缩写Oct 15, 2023。该格式被Git、Jenkins等工具原生支持,确保自动化构建时版本追溯准确。」
并检查所有修订记录,统一为2023-10-15格式。

6. 答辩验收指南:用三张表+一次演练搞定导师所有追问

6.1 验收清单表:对照文档目录逐项确认交付物完整性

答辩前,用此表自查文档是否覆盖所有必需模块。打钩项需在文档中找到对应页码,未打钩项立即补全:

序号验收项文档位置(页码)是否完成说明
1版权声明与密级标识(内部公开√)第1页☑密级勾选正确,无绝密/机密误选
2Revision Record含至少2次修订(初稿+审核)第2页☑2001-06-08 v1.00初稿、2001-06-15 v1.01审核
3需求描述含性能指标(在线用户200、导入≤20秒)第6页☑Service Performance Lists表格完整
4系统结构图(图1)与MVC分层文字说明第9-11页☑图与文字描述一致,无矛盾
5用户管理模块类图(含UserPage→UsersManager→UserDaoImpl)第13页☑依赖箭头方向正确,无循环依赖
6updateUser()方法的Input/Exception/Return三栏完整第18页☑Input含邮箱正则,Exception列BusinessException
7t_user表字段说明含CHECK约束(locked_until)第15页☑表格末尾注明CHECK ((status=2 AND locked_until IS NOT NULL) OR ...)
8存储过程sp_update_user_status含事务与错误码第19页☑p_result_code返回码定义清晰
9索引设计表(含idx_status_updated)第15页末☑索引名、字段、场景三列齐全
10附录含缩略语清单(Tapestry/Spring/Hibernate全称)第5页☑Tapestry: Apache Tapestry, Java Web Application Framework

提示:导师最常抽查第5、6、7项。若类图缺失依赖箭头,或updateUser()的Exception栏空白,会被直接判定「设计未落地」。

6.2 答辩话术表:针对高频问题的30秒精准回答模板

将答辩可能被问的10个问题,浓缩为30秒内能说完的答案,背熟后自然流畅:

问题精准回答(30秒内)
Q1:为什么用Tapestry不用Spring MVC?「教学系统需Page与Logic彻底解耦。Tapestry的Page类不依赖HTTP Servlet API,教师可替换UserPage为MockUserPage模拟网络延迟,而Spring MVC的@Controller强绑定HttpServletRequest,替换成本高。」
Q2:DAO层为何不直接返回Pojo给Page?「避免LazyInitializationException。Tapestry渲染时Session已关闭,若Page直接调用user.getOrders()(一对多),Hibernate会报错。VO是无状态数据载体,确保渲染安全。」
Q3:status字段为何不用ENUM?「ENUM修改需ALTER TABLE锁表,教学系统需支持运行时扩展状态。TINYINT配合t_status_dict字典表,新增状态只需插字典表,零停机。」
Q4:存储过程里为何用OUT参数而非RETURN?「MySQL存储过程RETURN只能返回整数,而我们需要返回result_code(整数)和result_msg(字符串)。OUT参数支持多类型,且Service层jdbcTemplate.call()可直接映射。」
Q5:locked_until的CHECK约束如何保证?「MySQL 8.0.16+原生支持CHECK。我们已在CREATE TABLE语句中定义,INSERT/UPDATE时数据库引擎自动校验,比应用层校验更可靠。」
Q6:事务为何只在Service层开启?「事务边界需覆盖完整业务单元。updateUser()包含校验、DAO更新、日志记录三步,若在DAO层开启,日志失败会导致用户更新回滚,违反『日志可丢失,业务不丢』原则。」
Q7:UserVO和User字段为何不完全一致?「UserVO剔除了@Id、@Version等ORM注解字段,仅保留id,name,email等业务字段。这是DTO(Data Transfer Object)标准实践,避免前端意外修改主键。」
Q8:@PreAuthorize为何不放在Controller?「Controller应只做请求路由。@PreAuthorize在Service层,确保getAllUsers()无论被UserPage还是ReportPage调用,权限校验逻辑一致,避免重复代码。」
Q9:索引idx_status_updated为何是复合索引?「单一status索引区分度低(仅4值),ORDER BY updated_at会触发filesort。复合索引(status,updated_at)让WHERE status=3 ORDER BY updated_at DESC走index range scan,性能提升40倍。」
Q10:Revision Record为何用YYYY-MM-DD?「ISO 8601格式被Git、Jenkins等工具原生支持。2023-10-15可直接用于git log --since="2023-10-15",确保版本追溯自动化,符合CMMI V3.0文档管理要求。」

6.3 一次实战演练:用「用户禁用」全流程贯穿所有设计点

答辩时,主动提出用「教师禁用某学生账号」这一典型场景,串联所有设计模块,证明设计闭环:

  1. 触发:教师在UserPage.html点击「禁用」按钮,UserPage.onActionFromDisableButton()被调用
  2. 控制层:UserPage调用usersManager.disableUser(userId, operatorId)
  3. 服务层:UsersManagerImpl.disableUser()中,先校验userId存在,再调用sp_update_user_status(userId, 3, operatorId)
  4. 数据库层:存储过程执行UPDATE t_user SET status=3, updated_at=NOW(),并插入日志
  5. 验证:t_user.status=3且t_operation_log有新记录,UserPage重定向至用户列表页

全程强调设计决策:

  • 「禁用操作必须原子化,所以用存储过程而非多条SQL」
  • 「状态变更需记录日志,所以存储过程内INSERT与UPDATE同处一个事务」
  • 「UserPage不校验权限,因@PreAuthorize在disableUser()方法上,确保任何调用方都受控」

从那以后我每次写设计说明书,都强制走一遍「一个典型场景的端到端链路」,从Page层点击开始,到数据库落盘结束,中间每一步的输入输出、异常分支、事务边界都写进文档。这不仅能提前发现设计断点,更让答辩时面对导师追问,我能立刻翻到对应页码,指着表格说「您看这里,第18页,Exception栏写着...」。希望帮到你。

本文还有配套的精品资源,点击获取