每年毕业季计算机专业的学生十个里至少有一半在搜“基于SSMVUE的网上医院问诊系统”的源码和LW文档。这个题目看着常规但真要自己从零做框架整合、前后端联调、部署上线每一关都能拦下一批人。去年我完整做了一个这个系统从Maven坐标冲突踩到Token过期从VUE路由守卫调到Nginx反向代理中间走了不少弯路但也把所有关键环节摸透了。这篇就按我的实际做法把项目拆开揉碎讲清楚适合正在纠结毕设选题、或者已经买了源码却不知道怎么给老师讲明白的同学。你不需要精通Spring也不用是VUE大佬跟着这套思路走至少能把这个项目从头到尾代码写通、文档写满、答辩论据十足。1. 先说结论为什么我选SSMVUE做问诊系统1.1 毕业设计不是造火箭技术栈要稳网上医院问诊系统说白了就是一个“挂号图文咨询处方记录”的管理平台。这个选题好就好在它既有普通增删改查的基础操作又有问诊订单这种跨表业务流难度正好卡在“能讲出东西”和“不会把人写秃”之间。很多老师一听“在线问诊”就点头因为能聊的东西多——用户模块、权限模块、业务状态流转、消息交互每一项都够在答辩时问上一问。技术栈方面SSMSpring SpringMVC MyBatis虽然不算新但它是理解Java Web经典分层最好的骨架。Spring负责管理对象SpringMVC负责收请求和响应MyBatis负责数据库操作。你现在愿意花心思把SSM搞懂后面切SpringBoot就是换汤不换药。前端选VUE则是因为它组件化清晰适合单页应用而且现在的毕业设计如果还在用传统JSP套页面答辩时容易显得技术太旧VUE至少能展示前后端分离的设计思想。1.2 前后端分离到底分在哪很多同学在立项时根本说不清“前后端分离”是什么意思。简单说以前是一个Tomcat把所有事情干了前端页面是服务端渲染出来的你在浏览器里看到的HTML是从Controller里拼出来的。前后端分离之后后端只提供JSON接口前端用VUE框架在浏览器里生成页面然后通过axios请求后端拿到数据再渲染。分离开之后两个端可以独立开发独立部署前端跑在8080端口开发时后端跑在8081端口联调时后端要考虑跨域部署时前端放在Nginx上后端放在Tomcat里请求再通过代理转发。这一整套流程在毕设论文里就是一张现成的架构图老师必问所以建议你把这个作为核心逻辑记清楚。1.3 这个系统的核心价值在“业务闭环”如果只做用户表增删改查那和课程设计没区别。网上问诊系统的价值在于订单状态流转患者发起问诊医生接诊双方发消息医生写诊断建议患者查看结论。每一步都对应数据变化而且可能同时操作多张表。比如医生接诊时既要更新问诊订单的“状态”字段又要插入一条“医生已接诊”的系统提示消息还得记录接诊时间。这里面就需要事务而“事务”和“状态机”正是论文里的亮点也是毕业论文里能够用几百字去深挖的地方。2. 网上医院问诊系统的功能拆解用户、医生、管理员三条线2.1 患者端从注册到看诊的完整路径患者进来先注册账号。注册不要只放用户名密码还需要手机号、真实姓名以便作为医疗记录的身份标识。密码不能明文存至少要做一次MD5加盐盐可以是用户名或者随机字符串虽然MD5不算安全但毕设里足够展示你的安全意识。患者登录后主要操作包括选择科室内科、外科、皮肤科等浏览对应医生列表。查看医生排班信息选择某个时段预约问诊更简化的方式可以不需要排班直接选医生发起问诊。在问诊页面填写病情描述上传症状图片可选点击提交生成问诊订单。系统生成订单后患者进入“我的问诊”列表查看订单状态。医生回复后患者能看到聊天消息和最终诊断建议并能将诊断结果保存在健康档案里。我把聊天做成了“异步留言”而不是实时WebSocket因为毕业设计用WebSocket会让复杂度瞬间增加而且容易在部署时出一堆连不上、心跳断开的破事。异步留言足够演示流程论文里也能写清楚。2.2 医生端接诊与回复医生账号由管理员在后台创建并分配到科室。医生登录后看到一个问诊工作台列表里是所有待接诊的订单。点击“接诊”时订单状态从“待接诊”变成“问诊中”同时系统自动发送一条欢迎消息给患者。医生可以在每个订单下多次发送回复也能查看患者第一次填写的病情描述。等沟通到差不多医生点击“完成问诊”这时进入一个填写诊断结论的表单页面写诊断结果和建议用药保存之后订单状态变成“已完成”患者端就能看到这份处方了。这里要注意医生端的所有操作都要校验科室归属不能让一个外科医生去接皮肤科的病人。这个是权限控制的小细节答辩时能体现你对RBAC基于角色的访问控制的理解。2.3 管理员端后台管理和统计管理员的功能不需要花哨但必须有医生账号管理、科室管理、用户禁用/启用、问诊订单总览。我额外加了一个很简单的首页统计展示今天新增问诊数量、医生总数、患者总数。不需要做饼图折线图直接用几个数字卡片放上去就行这只是为了页面不空。如果时间充裕可以加一个“常见问题管理”就是给患者端提供一个帮助列表也算一个麻雀虽小的内容管理模块。不过这部分对答辩帮助不是特别大优先级可以放低。2.4 数据库表设计最少要有这十张表问诊系统看起来业务多其实落到表上并不复杂。我这里直接把我最终用的表结构列出来你可以按这张表清单去建库建表表名作用关键字段user系统用户患者、医生、管理员通用id, username, password, role, status, create_timedoctor医生扩展信息id, user_id, department_id, title, introdepartment科室表id, name, descriptionschedule医生排班表可选id, doctor_id, date, period, statusconsult问诊订单表id, consult_no, patient_id, doctor_id, status, chief_complaint, create_timechat_message问诊聊天消息表id, consult_id, sender_type, sender_id, content, create_timeprescription处方/诊断表id, consult_id, diagnosis, suggestion, create_timehealth_record健康档案表id, patient_id, record_content, create_timeadmin_log管理员操作日志id, admin_id, action, target, create_time表之间的核心关系是patient和doctor都对应user表的role区分consult关联patient和doctorchat_message挂在consult下面prescription与consult一对一。为了简化我把“管理员”也放进了user表靠role0/1/2区分。排班表如果你不做预约挂号可以砍掉但如果想展示功能齐全建议保留。实际操作时建议用Navicat新建数据库字符集选utf8mb4因为问诊内容可能包含中文标点和表情符号。字段命名一律用下划线风格与Java实体类驼峰对应在MyBatis配置中开启驼峰映射能省去不少别名。3. 后端SSM框架整合别再用SSH了SSM才是简洁暴力3.1 三层架构Controller、Service、Dao职责分离SSM框架的项目结构一般长这样src/main/java ├── com.example.hospital │ ├── controller // 控制层接收参数返回JSON │ ├── service // 业务层接口和实现 │ ├── mapper // MyBatis的Mapper接口 │ ├── entity // 数据库实体类 │ ├── interceptor // 拦截器比如登录拦截 │ ├── common // 统一返回结果、异常处理 │ └── util // 工具类我习惯在Controller里只做一件事接收参数、校验基本格式、调用Service、把结果包成统一的JSON返回。真正的业务判断全部放在Service里。比如“医生接诊”这个方法Controller里就一句PostMapping(/accept) public Result accept(RequestBody ConsultActionDTO dto, HttpServletRequest request) { Integer doctorId (Integer) request.getAttribute(userId); consultService.acceptConsult(dto.getConsultId(), doctorId); return Result.ok(); }而Service里的acceptConsult方法要写判断订单是否存在、判断医生是否属于该订单对应的科室、更新订单状态为“问诊中”、插入一条系统聊消息。这些步骤全部做完才算业务完成。分层的意义是让每个方法够短出了问题能直接定位。答辩时老师翻你的代码看到清晰的Controller→Service→Dao印象会好很多。3.2 Maven依赖版本对齐是第一个大坑网上买来的源码跑不起来八成是依赖版本冲突。我用的核心依赖清单如下以pom.xml片段形式给你参考properties spring.version4.3.18.RELEASE/spring.version mybatis.version3.4.6/mybatis.version jackson.version2.9.9/jackson.version /properties dependencies !-- Spring核心 -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency !-- MyBatis -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version1.3.2/version /dependency !-- JSON -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version${jackson.version}/version /dependency !-- 数据库与连接池 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.47/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.1.20/version /dependency /dependencies特别注意JDK版本如果是1.8用Spring4.3没问题如果你本机装了JDK11以上Spring4.3有些类会被Java模块系统拦住建议换成Spring5.2。另外mysql-connector-Java 5.1.47对应MySQL5.7没问题如果你数据库是MySQL8.x得用8.0.28以上同时驱动名要写成com.mysql.cj.jdbc.DriverURL里还要加serverTimezoneAsia/Shanghai否则会报时区错误。3.3 Spring配置文件XML尽量少注解尽量多SSM配置比较繁琐我最终用了“纯注解 少量XML”的方式。核心配置文件有三个applicationContext.xml配置数据源、事务管理器、Mapper扫描。spring-mvc.xml配置视图解析器、静态资源处理、JSON转换器、Controller扫描。web.xml配置DispatcherServlet和ContextLoaderListener。在applicationContext.xml里用context:component-scan base-packagecom.example.hospital /扫描除了Controller之外的Bean。spring-mvc.xml里再用一个context:component-scan base-packagecom.example.hospital.controller /避免重复加载。数据源直接初始化Druidbean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName valuecom.mysql.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/hospital?useUnicodetrueamp;characterEncodingutf8/ property nameusername valueroot/ property namepassword value123456/ /beanMyBatis的Mapper接口扫描用bean classorg.mybatis.spring.mapper.MapperScannerConfigurer注意不要重复扫描到Service层否则启动会有警告。3.4 SSM常用注解梳成表答辩随口就来下面的注解是我在项目里最常用的你背熟就行注解作用我的使用场景Controller标记控制器类返回页面时用本项目中极少RestController标记控制器类且所有方法返回JSON所有API接口类RequestMapping映射URL路径类上加/api/user方法上加/loginGetMapping/PostMapping限定请求方式查询用GetMapping修改用PostMappingRequestParam接收URL参数分页页码、idRequestBody接收JSON请求体登录、创建问诊订单时接收DTO对象ResponseBody方法返回值直接写进HTTP响应配合JSON返回Service声明业务层组件业务实现类Repository声明数据层组件Mapper实现类由MyBatis代理Autowired依赖注入注入Service或MapperTransactional开启事务接诊、完成问诊等方法很多同学搞不懂RequestBody和ResponseBody的区别一句话记忆前端传JSON给你用RequestBody收后端返回JSON给对方用ResponseBody发。如果你前端用axios的post发了一个JSON.stringify对象但忘记设置Content-Type: application/json后端就会收到一个空对象这是联调时最常出现的低级错误。3.5 统一返回体让前端少拆三种结构刚开始写接口每个Controller返回格式都不一样有的返回HashMap有的返回字符串有的直接返回实体类。前端联调会崩溃。我在项目里定义了一个Result类public class Result { private Integer code; // 1成功0失败401未登录 private String msg; private Object data; public static Result ok() { ... } public static Result ok(Object data) { ... } public static Result fail(String msg) { ... } }这样一个约定下来前端axios拦截器只看code字段判断业务成不成功HTTP状态码只负责网络层。后来我又加了一个全局异常处理器RestControllerAdvice把Service里抛出的业务异常统一转成Result.fail(msg)Controller代码干净很多。4. 数据库设计与事务问诊系统最容易出事的地方4.1 用户表和密码加密用户表是核心中的核心。我没有单独建patient表而是用一个user表通过role字段区分角色role 0管理员role 1患者role 2医生医生相关特有字段比如科室、职称放到doctor表里通过user_id关联。这样做的原因是登录时只需要查user表不用跨表而分区字段能让医生资料管理更灵活。密码存储我一直用MD5(password salt)模式。注册时生成一个随机盐存储盐和加密后的密码。登录时用同样的盐加密比对。代码可以写成public User login(String username, String plainPwd) { User user userMapper.findByUsername(username); if (user null) { throw new BusinessException(用户不存在); } String hashed MD5Util.md5(plainPwd user.getSalt()); if (!hashed.equals(user.getPassword())) { throw new BusinessException(密码错误); } return user; }4.2 问诊订单的状态机问诊订单的状态字段我设计成int类型因为int在数据库里排序快占用空间小而且方便在Java里用枚举映射。状态定义值含义前端显示0已提交等待支付若需要待支付1已支付等待医生接诊待接诊2医生已接诊沟通中问诊中3医生已填写诊断完成已完成4用户主动取消或超时关闭已关闭状态流转必须做校验状态2的订单不能再次进行“接诊”操作。我在Service里写了一个私有方法checkConsultStatus(consult, expectedStatus)每次更新前先比对避免并发情况下重复接诊。由于没有做支付系统我通常让学生把“支付”省略掉直接提交后状态就变成“待接诊”这样既减少对接第三方支付的麻烦又能保持流程完整。论文里可以写“支付模块为模拟实现”答辩老师一般不会追着不放。4.3 一个完整的事务案例医生接诊时同时干两件事医生点击接诊后端要做的事在代码里是这样的Transactional(rollbackFor Exception.class) Override public void acceptConsult(Integer consultId, Integer doctorId) { Consult consult consultMapper.selectById(consultId); if (consult null) throw new BusinessException(订单不存在); if (!doctorId.equals(consult.getDoctorId())) throw new BusinessException(无权接诊该订单); if (consult.getStatus() ! 1) throw new BusinessException(订单状态不正确); // 更新订单状态为问诊中 consultMapper.updateStatus(consultId, 2); // 插入一条系统消息告诉患者医生已接诊 ChatMessage msg new ChatMessage(); msg.setConsultId(consultId); msg.setSenderType(0); // 系统 msg.setContent(您的问诊已被医生接诊可以开始沟通啦。); chatMessageMapper.insert(msg); }这里的Transactional保证了“更新订单状态”和“插入消息”要么都成功要么都失败。如果没有事务万一第二条SQL失败订单就停在“问诊中”但患者收不到任何通知。答辩时老师很喜欢问“你为什么在这里加事务”你就把这两行数据的联系讲清楚就行。MyBatis的事务配置依赖Spring的事务管理器记得在配置里开启事务注解驱动bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/5. 前端VUE开发环境从Node安装到项目跑起来5.1 安装VUE CLI与创建项目前端开发机需要Node.js。我建议装Node 14或16不要追新装18以上因为部分老依赖编译容易报错。安装好Node后打开命令行执行npm install -g vue/cli vue create hospital-front创建过程中选择“Manually select features”勾选Babel、Router、Vuex可选、Linter。路由模式我选了history开发时用但部署到Nginx需要额外配置如果不想麻烦就选hash模式不用任何服务端配合。毕设项目用hash模式完全能演示URL里多个#不丢人反而省事。5.2 vue-router路由配置与动态路由router/index.js里我用了一个常规定义const routes [ { path: /login, component: () import(/views/Login.vue) }, { path: /home, component: () import(/views/Home.vue), meta: { requiresAuth: true } }, { path: /consult/:id, component: () import(/views/ConsultDetail.vue), meta: { requiresAuth: true } }, ];我使用了meta字段标记哪些页面必须登录然后在全局守卫里校验router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next(/login); } else { next(); } });这样就实现了“没登录不能进首页”的简单鉴权。如果你的项目有医生和管理员两套界面可以用动态路由登录时根据返回的role字段用router.addRoute动态添加对应权限的页面。这个点在答辩时可以几句话带过显得你考虑到了权限控制。5.3 axios请求封装拦截器统一处理前端我没有在每个页面直接用axios而是在src/utils/request.js里做了封装import axios from axios; import { Message } from element-ui; import router from /router; const request axios.create({ baseURL: process.env.VUE_APP_BASE_URL || /api, timeout: 10000, }); request.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] token; } return config; }); request.interceptors.response.use( response { const res response.data; if (res.code 401) { localStorage.removeItem(token); router.push(/login); return Promise.reject(new Error(登录已过期)); } if (res.code ! 1) { Message.error(res.msg || 请求失败); return Promise.reject(new Error(res.msg)); } return res.data; }, error { Message.error(网络异常); return Promise.reject(error); } ); export default request;这里有个细节请求拦截器把token放进Header的Authorization字段后端拦截器会检查这个字段。很多同学把token放进params或者body里又麻烦又不规范强烈建议用Header。5.4 页面组件化首页、问诊列表、问诊详情的思考前端页面我不建议一个页面写两千行而是拆成组件。比如首页可以拆成“科室卡片列表”“推荐医生列表”两个组件。问诊详情页拆成“消息展示区”“输入框”“医生信息卡片”三个部分。组件拆分后的好处是当医生端和患者端共用一个问诊详情组件时可以通过props传currentUserRole来显示不同的操作按钮。前端用Element UI做UI库表格、表单、弹窗都现成。重点要处理的是数据回显VUE的data里要初始化和后端字段同名否则页面上拿不到值却不报错。这个问题折磨了我一个晚上后来一律在后端返回的JSON里输出data字段前端所有渲染都用res.data.name这种形式结构对齐后问题就消失了。6. 前后端联调中的实战踩坑跨域、Session、时间格式6.1 跨域问题为什么前端请求后端总是404和500开发环境下前端跑在http://localhost:8080后端跑在http://localhost:8081浏览器会拦截跨端口的请求这就是跨域。我在后端用了最简单的方案在Controller类上加了CrossOrigin(origins *, maxAge 3600)所有接口都能被允许访问。但这样治标不治本部署时前端Nginx和Tomcat不同端口还是可能出问题。更稳妥的做法是在vue.config.js里配置开发代理devServer: { proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } }这样前端请求/api/user/login开发服务器会转发到http://localhost:8081/api/user/login。后端接口的路径前缀原本是/api/user/login代理转发时不需要去掉前缀只要你后端映射也是以/api开头就行。6.2 登录态Session在前后端分离下不靠谱改用TokenSSM传统项目登录后把用户信息放Session里但前后端分离后跨域请求Session不同步用起来很痛苦。解决办法是在后端配置一个登录拦截器登录接口成功后生成一个随机字符串token存到redis或数据库同时把用户id存起来。响应给前端token前端存localStorage。每次请求前端带上Authorization: token拦截器里根据token找到用户id放入request属性中Controller直接用。拦截器的配置需要实现HandlerInterceptor接口public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (OPTIONS.equalsIgnoreCase(request.getMethod())) return true; String token request.getHeader(Authorization); if (token ! null tokenRedisService.check(token)) { request.setAttribute(userId, tokenRedisService.getUserId(token)); return true; } // 返回401状态码 response.setStatus(401); response.setContentType(application/json;charsetutf-8); response.getWriter().write({\code\:401,\msg\:\未登录\}); return false; } }然后在spring-mvc.xml里注册拦截器并排除登录接口和静态资源。这样注销只需要把token从redis删掉比Session手动失效简单。6.3 LocalDateTime序列化问题前端拿到的日期变成数组或T串如果你用了Java 8的LocalDateTime直接用Jackson默认序列化前端得到的是2023-06-12T10:15:30这种格式不漂亮而且有时区差错。我引入了jackson-datatype-jsr310并在统一配置里加上Bean public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { return builder - { builder.simpleDateFormat(yyyy-MM-dd HH:mm:ss); builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); }; }其实更省事的方法是数据库里直接存datetime实体类用java.util.Date或String类型但既然是新项目建议还是用LocalDateTime面试时也有东西可聊。前端渲染时用day.js来格式化避免VUE中直接用Date对象的兼容问题。6.4 文件上传头像SpringMVC里multipart的坑我允许用户上传头像图片。后端必须在spring-mvc.xml里配置multipart解析器bean idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolver property namedefaultEncoding valueUTF-8/ property namemaxUploadSize value10485760/ /beanController方法参数用MultipartFile接收前端用FormData提交不要设置Content-Type让浏览器自动加boundary。有一次我前端手动设置了application/json后端死活收不到文件改成由浏览器自己处理就立刻好了。另外上传后的文件要保存到服务器本地目录同时数据库里只存文件访问的相对路径不要存BASE64不然数据库体积爆炸前端页面也会卡。7. 部署上线从开发机到学生服务器别再被“跑不起来”折磨7.1 后端打包War包还是Jar包SSM项目最常规的是打War包放到Tomcat的webapps目录下启动Tomcat自动解压。如果你的项目是Maven工程在pom.xml里加上packagingwar/packaging然后在IDEA的Maven面板执行clean packagetarget目录下就有一个hospital.war。如果项目配置了内嵌Tomcat的SpringBoot现在很多“SSM”题目的解决骨架其实是SpringBoot但这不是我们的路线也可以打Jar包。我这边按War包路线写。War包部署时有一个坑如果你本地的数据库地址写的是localhost:3306上传到服务器后需要改成服务器的MySQL地址如果数据库也在服务器上那localhost其实也行但密码必须一致。7.2 前端打包与Nginx部署前端在项目根目录执行npm run build成功后生成dist目录把这个目录上传到服务器的/opt/hospital/dist。然后配置Nginxserver { listen 80; server_name your_domain.com; root /opt/hospital/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8081/; } }注意try_files这句是history模式必须的否则用户刷新/home页面时会404。代理部分要把/api/开头的请求转发到后端Tomcat的8081端口并且proxy_pass后面的URI处理要看清楚——如果写了http://127.0.0.1:8081/那么请求/api/user/login会被转发成/user/login如果后端接口本来就叫/api/user/login这里要写http://127.0.0.1:8081/api/或者去掉前缀的写法我用的是后者因为后端Controller的映射确实带/api。7.3 数据库迁移在本地Navicat右键数据库选择“转储SQL文件”得到.sql上传到服务器后执行mysql -u root -p hospital hospital.sql导入时最容易出问题的是表编码。你本地建库如果用utf8上传到服务器MySQL可能变成latin1中文乱码。强烈建议建库语句显式加DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci并且在JDBC连接串里加characterEncodingutf8useUnicodetrue。7.4 学生服务器的内存优化很多同学买的最低配轻量服务器内存只有1GTomcat加MySQL很容易把内存吃爆然后系统变卡甚至OOM。我当时的做法将Tomcat的catalina.sh里JAVA_OPTS设置为-Xms256m -Xmx256m -XX:MaxPermSize128m。MySQL的my.cnf里增加skip-name-resolve禁止DNS反查能减少一次网络解析省一点资源。关闭MySQL的默认的performance_schema如果不需要能省几百MB内存。说实话毕设演示期间并发量极小优化不优化都能跑但如果老师现场让你展示服务器上运行的效果能打开就成功一半了。8. 论文LW文档与答辩你怎么把项目讲得值一个“优”8.1 文档结构别照抄模板要按项目重点排学校给的LW文档模板通常固定你可以在模板基础上微调。我的文档结构是这样安排的绪论背景、意义、国内外现状系统需求分析可行性分析、功能需求、用例图系统设计总体架构、功能模块设计、数据库设计系统实现重点写后端接口实现和前端页面实现系统测试功能测试、安全性测试总结与展望其中第3、4章是最重要的。数据库设计里贴出完整的E-R图以及每张主要表的字段说明字段说明可以用表格呈现表格里写清楚“字段名、类型、是否可空、说明”。系统实现部分不要只贴一堆代码要先写“本系统采用SSM分层架构Controller层负责常用参数校验Service层负责业务逻辑Mapper层负责数据持久化”再挑核心功能贴代码。8.2 架构图和时序图怎么画老师第一眼会看你文档里的图。最少要画三张图系统架构图从浏览器到VUE前端再到Nginx再到Tomcat再到MySQL用箭头标出流向。功能模块图从“用户端、医生端、管理端”三个分支画出子功能。问诊时序图患者提交问诊、系统创建订单、医生接诊、双方发消息、医生开诊断每一步的箭头和返回消息都画出来。画图工具用ProcessOn或draw.io导出的图片会很高清。老师如果问图里某个流程为什么这样设计你能用一句话解释这就是高分。8.3 答辩必问的问题清单我提前把老师可能问的问题列了二十条演练了几遍。常问的几个“为什么选择SSM而不直接上SpringBoot”答SSM能清晰展示Spring核心的IOC和AOP原理SpringBoot是集成封装毕设更强调理解基础框架。这算合格回答。“VUE和JSP有什么区别”答JSP是前后端不分离页面由服务器拼接VUE在浏览器端渲染是前端工程化的产物配合接口调用更利于维护。“用户登录的安全性怎么考虑”答密码MD5加盐存储后端拦截器校验token敏感操作做了角色权限判断。这已经足够。“如果并发一千个人同时问诊系统哪里会是瓶颈”答MySQL连接数、Tomcat线程池连接池配置不够会排队还可以说从单机扩展成集群加Redis做缓存通过MQ削峰。但别说得过深点到为止即可。“事务是怎么控制的”答Spring声明式事务用Transactional标记Service方法由DataSourceTransactionManager管理当方法中抛出异常时回滚所有数据库操作。如果老师问到具体源码你要能现场操作IDEA搜索某个方法然后顺着Controller一路讲到SQL。这个功夫平时多按F3搜几次就能熟练。我最后想说的是这个系统做完我最大的感受是毕设真正值钱的不是代码量而是你在开发中遇到问题、排查问题、解决问题的过程。我记得联调跨域那几天真的是不断翻浏览器控制台后来发现是前端代理指向写错了一个端口。这种教训远比你背多少面试题都深刻。如果你正在做这个题目我建议你别急着去找现成的成品先自己搭一遍SSM框架再写一个用户登录再扩展问诊模块。每一步都走一遍之后你就不怕答辩老师深挖了。最后再分享一个让老师好感度暴涨的小技巧在你的项目里写一个醒目、完整的README文档把如何导入数据库、启动后端、启动前端、测试账号密码全部写清楚。老师拿到你的源码后如果五分钟内能成功跑起来印象分会直线上升。这也是我踩过坑之后强烈推荐你花十分钟做的事。