课程总结
本学期,我有幸作为后端开发负责人,深度参与了“应急演练系统”的从零到一的构建过程。该系统旨在为各类组织提供一个集演练计划、任务派发、过程执行、评估反馈及模板管理于一体的综合性平台,其用户终端覆盖Web端与App端。我主要负责后端整体架构设计、数据库建模、API接口开发与部署维护工作。项目技术栈选用了看似“复古”但极具挑战性的 Java Servlet、Vue3 与 MySQL 的组合。这份总结将系统性地复盘我的开发历程,深入剖析核心模块的实现细节、攻克的技术难点,并沉淀项目带给我的反思与成长。
在项目启动之初,我们对技术选型进行了审慎的考量。
我们没有选择业界流行的 Spring Boot 等高度集成的框架,而是回归本源,采用了 Java Servlet 作为后端核心。这一决定的主要原因有三:首先,它能让我们团队更深入地理解Web请求的完整生命周期,从HTTP协议解析到请求分发、处理与响应,夯实底层基础;其次,对于本项目的中等规模而言,Servlet足够轻量,避免了“杀鸡用牛刀”的过度工程化;最后,它强制我们手动处理依赖、配置和底层交互,带来了无与伦比的学习价值。当然,这也意味着我们需要编写更多的模板代码,并自行处理过去由框架代劳的诸多事务,如依赖注入、事务管理等。
为保证代码的清晰度、可维护性和可扩展性,我设计了经典的三层架构模式:
数据访问层 (DAO - Data Access Object)**:封装所有对 MySQL 数据库的原子操作(增删改查)。通过 JDBC 和 Druid 连接池与数据库交互,将SQL语句与Java业务代码解耦。
业务逻辑层 (Service):负责处理核心业务逻辑。它调用一个或多个DAO方法,组合成一个完整的业务功能,并在此层面处理数据库事务,确保业务操作的原子性。
表示层 (Servlet):作为MVC模式中的控制器(Controller),直接与前端 Vue3 应用进行HTTP通信。每个Servlet负责一个特定的功能路径(如 /plan, /task 等),它接收前端请求、解析参数、调用相应的Service方法处理业务,最后将结果封装成统一的JSON格式返回。
此外,我们大量使用了 `Filter` (过滤器) 和 `Listener` (监听器) 来处理横切关注点。例如,创建 `EncodingFilter` 来统一全站的字符编码为UTF-8,防止乱码;`AuthFilter` 用于验证每个受保护API请求头中的JWT令牌,实现统一的身份认证与授权;`ContextListener` 在服务器启动时初始化数据库连接池等全局资源。
数据库设计遵循第三范式(3NF),旨在减少数据冗余,保证数据一致性。以核心的“演练计划”与“演练任务”为例,我设计了如下关键表:
`t_drill_plan` (演练计划表):存储计划的基本信息,如名称、目的、创建时间、状态(草稿、待审批、已批准、进行中、已完成)等。
`t_drill_task` (演练任务表):存储从计划分解出的具体任务,通过 `plan_id` 外键与计划表关联。
t_task_assignment (任务分配表):一个中间表,用于实现任务与执行人员(t_user)的多对多关系,记录了 task_id 和 user_id。
这种设计保证了数据结构的规范化,并通过外键约束维护了数据的完整性。
- 演练计划管理与状态机:
演练计划的生命周期管理是系统的核心。我并未简单地使用字段来标记状态,而是在Service层实现了一个有限状态机(Finite State Machine, FSM)。当用户对一个计划进行操作时(如提交审批),PlanService会首先检查当前状态是否允许该操作,然后才更新状态字段。这有效防止了非法的状态跳转(例如从“草稿”直接变为“已完成”),保证了业务流程的严谨性。
演练执行期间,前端需要实时了解任务状态。考虑到Servlet技术栈的特点和项目复杂度,我们放弃了WebSocket,转而采用了一种优化的长轮询(Long-Polling)机制。前端Vue3应用会向一个专门的TaskStatusServlet发起AJAX请求。后端接收到请求后,若没有状态更新,则会hold住连接30秒;一旦有任务状态变化,立即返回最新数据,并关闭连接。前端收到响应后,会立刻发起下一次请求。这种方式在实现准实时效果的同时,也比传统的短轮询节省了大量的无效HTTP请求开销。
模板上传功能要求处理文件流。我利用Apache Commons FileUpload组件来解析multipart/form-data请求。为保证安全,所有上传的文件都不会直接使用原始文件名存储,而是通过UUID生成一个唯一的文件名,并将原始文件名、文件类型、大小和这个UUID的对应关系存入MySQL的t_template_file表中。文件本身则存储在Web应用根目录之外的物理路径,彻底杜绝了通过URL直接访问上传文件的风险。
在无框架环境下,我手动构建了一套基于JWT(JSON Web Token)的无状态认证体系。用户登录成功后,LoginServlet会生成一个包含用户ID和角色的JWT,并设置有效期,然后返回给前端。前端在后续的每次请求中,都需要在Authorization请求头中携带Bearer <token>。后端的AuthFilter会拦截所有需要保护的API请求,解析并验证JWT的签名、时效性。若验证通过,则将用户信息存入ThreadLocal,方便后续业务逻辑直接获取,实现了安全的、无服务器会藩的会话管理。
与Spring的@Transactional注解不同,在Servlet项目中,事务管理需要显式编码。在Service层的每个业务方法中,我都遵循了“开启事务 -> 执行操作 -> 提交/回滚 -> 关闭连接”的模式。通过connection.setAutoCommit(false)开始事务,在try-catch-finally块中执行多个DAO操作,成功则在try的末尾调用connection.commit(),若捕获到任何异常,则在catch块中调用connection.rollback(),保证了跨多个数据表操作的原子性和一致性。
为应对潜在的并发压力,我实施了多项性能优化。首先,配置并使用了Druid数据库连接池,替代了原始的JDBC连接方式,极大地减少了数据库连接创建和销毁的开销。其次,我实现了一个简单的内存缓存。对于那些不常变动但查询频繁的数据,如用户角色权限、系统配置项等,我使用一个ConcurrentHashMap将其缓存在内存中,并设定了定时刷新策略,有效降低了数据库的读取压力。
这次“刀耕火种”式的开发经历,让我获益匪浅。我不再仅仅是框架的使用者,而是真正深入到了Web应用的底层,对Servlet容器的工作原理、HTTP协议的交互细节、手动管理事务和安全性的复杂性有了刻骨铭心的理解。这段经历极大地锻炼了我的问题解决能力和系统设计能力。同时,我也深刻体会到框架的价值——它们将开发者从繁琐的底层细节中解放出来,让我们能更专注于业务逻辑本身。
在团队协作方面,由于前后端接口定义完全依赖于文档和口头约定,我认识到制定一份清晰、稳定且易于遵循的API契约(如 OpenAPI 规范)是何等重要,它能有效减少沟通成本和集成错误。
总而言之,本学期的应急演练系统后端开发是一次极具价值的实践。我们成功地在有限的技术栈上构建了一个功能完备、运行稳定的系统,满足了项目预设的全部需求。
展望未来,若项目继续演进,我会建议将系统迁移至 Spring Boot 框架,以利用其生态优势,提高开发效率、可维护性和可测试性。同时,可以引入 WebSocket 或 SSE 替代长轮询,以实现更高效的实时通信。数据分析模块也可以引入更专业的工具,进行深度挖掘,为演练评估提供更智能的决策支持。这次宝贵的开发经历为我未来的技术生涯打下了坚实的地基。
给老师的建议
1.老师上课没给出明确的学习路径,突击学习的强度实在是太大了,老师给的学期学习框架太笼统,希望老师给出一个一学期的树状指挥图。
2.希望上课上厕所老师不要再罚站,罚站实在是夹不住,这个是个很大的问题。
3.周六讲座这个时间不妥当,内容精彩,但是时间阴间,换个时间就好了
浙公网安备 33010602011771号