资讯详情

资讯详情

基于SpringBoot+Vue+MySQL的社区医疗服务可视化管理系统设计

拿到这个标题我第一反应是又是毕设季最经典的那种“前后端分离 管理系统 可视化”三板斧组合。但仔细一看这个其实是把两个很多同学容易做砸的东西放在了一起一是社区医疗服务这个业务场景的贴合度二是“可视化”不能只做一个静态图。SpringBoot Vue MySQL这套技术栈太经典了反而是经典的组合更容易让人觉得“随便抄抄就行”结果答辩被问两句就漏了底。这篇文章我从选型、数据库、后端、前端到部署和答辩逻辑全部过一遍全部基于自己实际做过的项目经验来写给正在做类似毕业设计或想练全栈的同学一个能直接落地的参考。后面没有废话直接进正题。1. 项目到底在做什么需求拆解与功能边界1.1 社区医疗服务场景的真实痛点社区医疗服务系统和不医院挂号系统有个很大的区别它的核心服务对象是社区常驻居民服务模式偏向“慢病管理日常健康服务健康档案”而不是“急诊住院大型手术”。换句话说居民到社区服务中心来更多是量血压、测血糖、定期随访、基础体检、开常用药、预约疫苗接种这类轻医疗服务。我之前帮一位在社区服务中心工作的朋友梳理过实际工作流程发现他们日常大量工作是真的还在手工登记居民来了先翻纸质档案医生看完病再手动补录电脑。数据一多回头要统计某个小区的老年人高血压占比、某个月的糖尿病患者随访率得人工筛半天。这个系统表面看是做“可视化管理”本质上是把社区医疗服务流程中“信息断档”的问题补上居民基础档案电子化不再抽一个档案袋翻十分钟预约、随访、用药记录留痕健康数据能按时间轴聚合管理者需要一个公共卫生视角的数据看板比如重点人群分布、就诊趋势为社区健康干预提供依据。所以你在做这类项目给自己定位的时候不要把它理解成“像美团一样的医疗预约App”它是一个“以居民健康档案为核心以社区服务流程为链条以管理看板为决策辅助”的综合管理平台。1.2 角色划分与功能矩阵社区医疗系统的角色划分一般来说是三到四类管理员、医护人员医生/护士、居民患者、如果有药品还需要一个库房角色的权限但毕设通常没必要拆那么细四类显得冗余三类刚好。我建议的角色设定系统管理员负责用户管理、科室信息维护、基础数据字典维护、公告发布拥有系统全部菜单权限。医护人员负责居民档案录入与更新、健康随访记录、门诊/接诊记录填写、预约审核处理、药品查询。医生进入系统看到的主要是待办和病历操作台。居民用户负责查看健康档案、预约挂号、查看个人就诊记录和随访建议。居民通过前台界面自注册或者由管理员直接建档开通账号。围绕这三类角色功能边界划分起来比较清晰功能模块管理员医护人员居民用户管理支持只读不支持居民健康档案全部权限新增/编辑查看本人预约挂号管理查看全部审核/处理预约发起/取消预约门诊接诊记录查看全部新增/修改查看本人健康随访管理查看全部新增/编辑查看本人药品信息管理全部权限查看不支持数据统计可视化全部报表部分报表个人健康图表公告信息发布/编辑查看查看有了这张矩阵你做数据库设计的时候就不会漏表也不会一个功能点搞出四五张表来。1.3 功能边界的取舍什么该做什么不该做做毕设最忌讳的就是什么都想往里塞。我看到太多同学把“社区医疗服务”做成了“综合医疗系统”又是排班、又是住院管理、又是线上支付最后结果是每张表都有每个功能都是半成品。我自己的建议是砍掉这几块完全别碰在线支付涉及第三方接口对接、退款、对账工作量爆炸答辩还不好讲。复杂的权限框架如菜单级RBAC动态路由社区系统用户量不大用角色字段做简单路由拦截完全够用。消息推送/短信不要做技术难度高评审老师也知道毕设做不出生产级推送。在线问诊聊天很多人想做但是WebSocket、消息记录、会话管理牵扯面太大。反而建议把省下来的时间投到“可视化”和“数据统计”上这两块做深了无论是页面效果还是答辩讲技术方案都是很大的加分项。2. 技术栈选型每个选择的背后逻辑2.1 后端为什么是SpringBoot而不是SSH或SSM这个其实不用多解释SpringBoot在当下已经属于Java后端开发的绝对主流。但很多同学在写论文的时候只知道“SpringBoot简化了配置”写不到点子上。你答辩前一定要把这个理由讲得能让评审老师点头嵌入式的Tomcat不用单独部署WAR包前后端分离项目通过一个可执行Jar包就能跑起来部署成本低Starter机制把常见三方库的依赖冲突问题基本消灭了比如你要集成MyBatis就加一个mybatis-spring-boot-starter要做参数校验就加spring-boot-starter-validation自动配置加外部化配置application.yml环境切换非常方便开发环境、生产环境用不同Profile生态成熟遇到任何问题搜索解决方案基本都有现成答案。我见过一个同学因为守旧选了SSM框架结果光Spring和MyBatis的XML配置就堆了七百多行答辩时连他自己都解释不清换环境之后为什么数据库连接串总报错。以学习为目的还能接受以“在最短时间内做出稳定可演示系统”为目标还是SpringBoot更合理。2.2 前端为什么是Vue可视化为什么必须配ECharts前端选Vue的核心原因是它对新手最友好模板语法接近原生HTML渐进式引入不需要一开始就啃复杂的工程化概念。当然了现在官方推荐Vue 3加组合式API如果你是从零开始学那直接用Vue 3加Element Plus也没问题。但如果你项目是参考旧开源项目大概率是Vue 2加Element UI这个也不要慌两者核心思想一致对毕设来说完成功能优先。重点说说可视化选型。ECharts在这个场景下是唯一合理的选择纯前端渲染无后端依赖Apache基金会维护文档和示例非常全折线图、柱状图、饼图映射社区健康数据非常合适而且渲染效果在同类框架里算非常精致的不需要额外写后端图表API数据从后端接口拿JSON经过绘制配置转成图表即可。特别是ECharts有“数据集”这个概念可以非常自然地把后端返回的数据绑定给图表比如这样myChart.setOption({ dataset: { source: res.data }, xAxis: { type: category }, yAxis: {}, series: [{ type: line, smooth: true, encode: { x: month, y: count } }] });后端只需要返回一个包含month和count的数组前端就可以动态渲染。2.3 数据库为什么是MySQL不是PostgreSQL和Oracle很多同学写论文喜欢写“MySQL的优点”背出来的全是官方宣传语开源、免费、跨平台……说实话这些都不是选型理由。你选MySQL最实际的原因是社区资料最多遇到sql_mode问题、字符集问题、索引失效问题搜出来的解决方案命中率高学校机房和导师默认的就是MySQL用其他库反而增加环境兼容风险Navicat和DataGrip对MySQL的支持非常成熟导出SQL脚本给导师检查也非常方便社区医疗这个数据量级几百张表、几万到几十万条记录对MySQL来说毫无压力。你真正要在论文里写的是“MySQL 8.x的窗口函数和JSON能力足以支撑统计查询”这句话比空泛的“免费开源”有信息量得多。2.4 权限认证方案JWT还是Session技术选型里还有一个容易被追问的点登录态怎么保持。我明确推荐JWTJSON Web Token方案理由有三条前后端分离环境下Session共享需要额外解决跨域Cookie携带问题JWT无状态前端存储后每次请求放在Header里带过来避免了很多麻烦JWT能承载用户基本信息用户ID、角色后端不查库就能识别身份它是目前主流的后端认证方式写在简历上或答辩里都不会被挑刺。但这不代表JWT没问题。要注意JWT的有效期和续签策略不设过期时间或者设一天都会影响使用体验。建议AccessToken设两小时过期前端配合Axios拦截器检测到401时重新走一次登录流程。3. 数据库设计核心表结构和ER关系拆解3.1 表清单最少需要哪些数据表数据库设计是这个项目最先需要稳住的环节。我见过太多的项目做到一半疯狂加字段改表结构就是因为一开始没把实体关系想清楚。按照上面的功能矩阵我的表设计建议如下sys_user用户表登录账号、密码、姓名、角色、手机号、状态health_record居民健康档案表身高、体重、血型、既往病史、过敏史、家族病史doctor_info医护人员信息表科室、职称、简介department科室表内科、外科、全科……appointment预约挂号表预约人、预约医生、预约时间、时段、状态clinic_record门诊/接诊记录表就诊人、就诊医生、诊断结果、症状描述、处方follow_up_record健康随访记录表随访居民、随访日期、随访类型、结果描述medicine药品信息表药品名称、规格、库存、价格、生产厂家notice公告表标题、内容、发布时间、发布人dict_data/dict_type可选数据字典表用于维护科室分类、预约时段等公共数据差不多十张表上下既有业务主线的深度又没有膨胀到失控。3.2 核心表结构字段设计参考以健康档案表为例字段设计要兼顾完整度和冗余度字段名类型说明idbigint(20)主键自增user_idbigint(20)关联用户表IDnamevarchar(50)居民姓名gendertinyint性别 0男 1女id_cardvarchar(18)身份证号birth_datedate出生日期heightdecimal(5,2)身高cmweightdecimal(5,2)体重kgblood_typevarchar(10)血型allergy_historyvarchar(255)过敏史past_historyvarchar(500)既往病史family_historyvarchar(500)家族病史create_timedatetime创建时间update_timedatetime更新时间这几个字段覆盖了体检类数据的最核心部分如果你还想做“老年人健康评估”或“慢病管理”的可视化这里一定要留出“是否慢病”“慢病类型”等标志字段——统计高血压患病率这类图表就是靠它出数据。3.3 外键到底建不建索引怎么加很多教学大纲还在强调外键约束但在实际开发里外键就是个隐患。原因很简单如果订单被预约挂号的逻辑依赖了外键级联删除删除用户的时候会把预约记录一并删掉属于静默数据丢失。社区医疗系统中档案关联的数据非常多用户删除如果级联删档案会导致统计维度缺失。我的做法是保留字段关联比如health_record.user_id但不建物理外键约束在逻辑层控制数据完整性。这样做的好处插入和更新速度快一点不用每次做外键校验数据清洗或批量导入不受外键掣肘删除操作可以自定义逻辑比如将用户状态置为禁用而不是物理删除。索引方面预约表的doctor_id、visit_date档案表的user_id都是高频查询字段一定要建普通索引。另外如果系统里做了按身份证号查重那id_card字段必须建唯一索引否则用户重复建档的脏数据能让你崩溃。4. 后端核心实现接口设计、JWT、统计SQL4.1 统一返回结果R对象设计一个管理系统不管功能多少接口风格一定要统一。我自己固定用的是R对象作为所有接口的返回结构Data public class RT { private Integer code; private String message; private T data; public static T RT success(T data) { RT r new R(); r.setCode(200); r.setMessage(操作成功); r.setData(data); return r; } public static RVoid error(int code, String message) { RVoid r new R(); r.setCode(code); r.setMessage(message); return r; } }这样做的好处是前端Axios拦截器可以统一判断code来弹出错误提示不用每个接口单独写try catch。全局异常处理器配合后效果更好RestControllerAdvice public class GlobalExceptionHandler { ExceptionHandler(BusinessException.class) public RVoid handleBusinessException(BusinessException e) { return R.error(e.getCode(), e.getMessage()); } ExceptionHandler(MethodArgumentNotValidException.class) public RVoid handleValidationException(MethodArgumentNotValidException e) { String msg e.getBindingResult().getFieldError().getDefaultMessage(); return R.error(400, msg); } }这种约定一旦定下来后面写所有Controller都只会返回R.success(...)代码会非常清爽。4.2 JWT登录鉴权的真实写法JWT的坑主要在配置和拦截器上。Spring Boot 3里集成JWT加依赖的时候注意jjwt版本要选0.11以上的否则部分类名都不一样。核心思路走一遍第一步生成Token的工具类public class JwtUtils { private static final String SECRET your-256-bit-secret-key; private static final long EXPIRE 1000 * 60 * 60 * 2; public static String createToken(Integer userId, String role) { return Jwts.builder() .claim(userId, userId) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRE)) .signWith(Keys.hmacShaKeyFor(SECRET.getBytes()), SignatureAlgorithm.HS256) .compact(); } public static Claims parseToken(String token) { return Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(SECRET.getBytes())) .build() .parseClaimsJws(token) .getBody(); } }第二步登录接口签发TokenPostMapping(/login) public RMapString, Object login(RequestBody LoginDTO dto) { User user userService.login(dto.getUsername(), dto.getPassword()); String token JwtUtils.createToken(user.getId(), user.getRole()); MapString, Object map new HashMap(); map.put(token, token); map.put(userInfo, user); return R.success(map); }第三步拦截器校验Tokenpublic class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); // 判空、解析、失败返回401 Claims claims JwtUtils.parseToken(token); request.setAttribute(userId, claims.get(userId)); return true; } }第四步注册拦截器并配置放行路径Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new JwtInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/api/login, /api/register); } }这里有个细节跨域时预检请求OPTIONS也会经过拦截器如果不对它放行前端会一直报跨域错误。必须拦截器放行OPTIONSif (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; }这行代码能救不少人的命。4.3 预约挂号核心流程怎么串起来预约挂号是整个项目业务逻辑最复杂的点之一状态流转必须设计清楚否则逻辑会乱。我的建议是预约表设计以下状态字段0待确认1已确认2已完成3已取消居民在前台发起的预约默认状态待确认医生可以确认接诊或取消预约居民也可以取消未开始的预约。这里面的关键逻辑是“号源防重复”。我建议用唯一索引来兜底在数据库层就干掉重复预约的可能性ALTER TABLE appointment ADD UNIQUE KEY uk_doctor_time (doctor_id, visit_date, time_slot, patient_id);这个MySQL字段加对之后即便并发请求同时打到后端数据库也会拒绝重复条目是比应用层synchronized更可靠的防重方案。4.4 统计可视化页面的SQL怎么写可视化不是前端画画就完了后端要能给出高性能的聚合数据。三个核心场景场景一近12个月就诊量趋势。SELECT DATE_FORMAT(create_time, %Y-%m) AS month, COUNT(*) AS visit_count FROM clinic_record WHERE create_time DATE_SUB(CURDATE(), INTERVAL 12 MONTH) GROUP BY DATE_FORMAT(create_time, %Y-%m) ORDER BY month;这里用DATE_FORMAT在MySQL里做按月分组合适数据量小比在Java里做循环统计效率高得多。如果你的MySQL版本到8.0还可以用DATE_TRUNC的替代语法效果一样但考试和论文里写DATE_FORMAT更通用。场景二各科室就诊占比。SELECT d.name AS department_name, COUNT(c.id) AS total_count FROM clinic_record c LEFT JOIN doctor_info di ON c.doctor_id di.id LEFT JOIN department d ON di.department_id d.id GROUP BY d.name;这种统计会引出ECharts的饼图前端不用做任何二次聚合。场景三慢病人群年龄分布。就像前面说的如果档案表里存的是出生日期那统计就要先算年龄再分组SELECT CASE WHEN TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) 18 THEN 0-17 WHEN TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) 45 THEN 18-44 WHEN TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) 65 THEN 45-64 ELSE 65以上 END AS age_group, COUNT(*) AS user_count FROM health_record WHERE is_chronic 1 GROUP BY age_group;这样的SQL在答辩的时候也特别好讲评审老师一看就知道你真的懂业务。5. 前端核心实现管理后台与可视化看板5.1 前端路由设计一页一模块很多同学前端页面放了无数个vue文件但实际上路由和菜单嵌套关系都没梳理好。做管理系统路由用懒加载按模块划分const routes [ { path: /login, component: () import(/views/Login.vue) }, { path: /layout, component: () import(/layout/index.vue), children: [ { path: dashboard, name: Dashboard, component: () import(/views/dashboard/index.vue), meta: { title: 数据看板 } }, { path: appointment, name: Appointment, component: () import(/views/appointment/index.vue), meta: { title: 预约管理 } }, { path: records, name: Records, component: () import(/views/healthRecord/index.vue), meta: { title: 健康档案 } } // 其他模块 ] } ];前端路由的meta.title和侧边栏菜单联动最终生成的效果就是面包屑和菜单标题统一不会出现菜单叫“健康档案”进来页面标题是“Dashboard”这种低级问题。5.2 Axios封装拦截器做三件事Vue项目里Axios一定要封装不封装的直接后果是每个页面里面都有一大堆重复的loading逻辑和状态判断。我的封装固定做三件事第一请求拦截器带Tokenservice.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization token; } return config; });第二响应拦截器统一处理401和业务错误service.interceptors.response.use( response { const res response.data; if (res.code ! 200) { if (res.code 401) { router.push(/login); } ElMessage.error(res.message); return Promise.reject(new Error(res.message)); } return res.data; }, error { ElMessage.error(网络请求异常); return Promise.reject(error); } );第三API接口统一在独立的js文件里管理比如api/appointment.jsexport function getAppointmentList(params) { return request({ url: /api/appointment/list, method: get, params }); } export function confirmAppointment(id) { return request({ url: /api/appointment/confirm/${id}, method: put }); }这么做的价值前端组件里永远只调接口函数不自己拼URL全局改接口版本号的时候只动封装文件维护成本低到极致。5.3 ECharts可视化看板的实现思路就我的经验来说可视化看板是决定整个毕设“看起来高级不高级”的核心页面。视觉上做得好装修分数蹭蹭往上涨。但实现复杂度并不高。第一步安装EChartsnpm install echarts第二步封装一个图表组件基础折线图的逻辑长这样script setup import * as echarts from echarts; import { ref, onMounted } from vue; const chartRef ref(null); let myChart null; onMounted(() { myChart echarts.init(chartRef.value); const option { title: { text: 近12个月就诊趋势 }, tooltip: { trigger: axis }, xAxis: { type: category }, yAxis: { type: value }, series: [{ name: 就诊量, type: line, smooth: true, areaStyle: {} }] }; // 从后端API拉数据 getVisitTrend().then(data { option.xAxis.data data.map(item item.month); option.series[0].data data.map(item item.visit_count); myChart.setOption(option); }); }); /script template div refchartRef stylewidth: 100%; height: 360px;/div /template第三步看板页面用栅格布局做成一个两行两列的“仪表盘”效果左上就诊量趋势折线图月份X轴数量Y轴右上科室就诊占比饼图左下居民年龄分布柱状图右下药品库存预警表格或横向条形图这样一块大屏配合合适的颜色和间距放答辩PPT里就是最好的框架图支撑材料。ECharts图表的自适应别忘了加window.addEventListener(resize, () myChart.resize());不然演示时你拖拽浏览器窗口大小图表就变成了半个身子非常尴尬。5.4 表单校验用细节提升系统完整度很多同学做的后台表单点完能提交但一到异常输入就露馅。评审老师最爱的操作就是随便填几个非法字符再点提交看反馈。为了应对这一点前端表单校验不能只依赖HTML的required。我推荐Vue用Element的rules配置const rules { phone: [ { required: true, message: 手机号不能为空, trigger: blur }, { pattern: /^1[3-9]\d{9}$/, message: 手机号格式不正确, trigger: blur } ], idCard: [ { required: true, message: 身份证号不能为空, trigger: blur }, { pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: 身份证格式不正确, trigger: blur } ] };这种做法一小段代码就能配合后端参数校验堵住绝大多数非法输入。好的毕设项目不需要面面俱到但关键输入位置必须有反馈这决定了你在答辩演示环节的“可控感”。6. 部署与联调本地跑通一次完整流程6.1 开发环境版本选择版本选对有安定心神的作用。根据项目和现在主流教程的配置我建议按下面这个清单装JDK8 或 17 都可以。如果用的是Spring Boot 3.x必须JDK 17如果用的Spring Boot 2.7JDK 8完全够。考虑到代码生成器兼容性Spring Boot 2.7 JDK 1.8的组合是最稳的。MySQL5.7 或 8.0如果是自己新装的建议直接8.0。Node.js14.18 或 16.x适配Vue 2项目Vue 3的话建议Node 16。包管理器npm 或 yarn均可别混用。开发工具IntelliJ IDEA 后端 VSCode 前端或者全部用IDEA也行。6.2 项目初始化的几个关键步骤第一步后端用Spring Initializr生成基础工程勾选Spring Web、MySQL Driver、MyBatis、Lombok、Validation这几个依赖是核心。不需要一开始选一堆没用的中间件比如Redis和消息队列加依赖容易排除依赖难。第二步配置文件里注意时区问题spring: datasource: url: jdbc:mysql://localhost:3306/community_health?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/ShanghaiserverTimezone不配启动直接报时区错误Jackson日期格式不配前端拿到的时间就是一串时间戳还得每个接口单独处理所以这两行一定在一开始就配上。第三步前端初始化Vue项目推荐用Vite加Vue 3比Webpack构建快好几倍。如果项目模板是Vue 2那保留Webpack也行但新建直接Vite。6.3 前后端联调与跨域跨域的根因不在后端而在浏览器的同源策略前端跑在8080端口后端跑在9090端口两者端口不一样浏览器就认为是跨域直接拦掉请求。解决方式三个第一后端配置全局CORSConfiguration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }第二前端Vite开发环境配置代理这是开发期最顺手的方案生产环境也不用改代码// vite.config.js server: { port: 8080, proxy: { /api: { target: http://localhost:9090, changeOrigin: true } } }这样前端代码里请求/api/login开发服务器自动转发到后端9090端口。生产部署时把打包后的静态文件放在Nginx里再配一个反向代理到后端服务效果和开发期一致。两个方案双保险建议都用上开发阶段用Vite代理部署阶段用Nginx反向代理性能和灵活性都兼顾。第三如果只是临时测试后端把CORS配置配好前端直接写完整URL请求也可以但这样不是好习惯上线必踩坑趁早用代理方案。6.4 打包部署要点回顾后端打包mvn clean package -DskipTests然后java -jar target/community-health.jar就能跑。如果数据库不在本机注意打包前把application.yml里的数据库地址改成部署机器的可访问地址。前端打包npm run build生成dist目录把里面的静态文件扔到Nginx的html目录下Nginx配置里加一条代理规则把/api转发到后端server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:9090/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里有一行很关键try_files $uri $uri/ /index.html;。如果不写这一行刷新页面到二级路由时Nginx会报404。好多同学部署完发现首页能打开一刷新就白屏就是这个配置缺失。7. 常见问题和排查技巧实录7.1 前端页面白屏和控制台报错大概率是路由模式问题。Vue Router的history模式在Nginx下需要依赖后端配置如果你不想过多配Nginx直接改用hash模式最省心createWebHashHistory()URL虽然带个#但部署在任何静态服务器都不需要额外配置。毕设演示完全能接受。如果需要美观再上history模式配Nginx。7.2 后端启动报数据库连接失败先检查MySQL有没有启动再核对application.yml里的密码和端口。另外注意MySQL 8.0的驱动名是com.mysql.cj.jdbc.Driver低版本驱动连8.0数据库会报加密协议错误。这一步如果没注意能卡一整天。7.3 MyBatis查询结果中文乱码一句话在MySQL连接URL上加characterEncodingutf8同时在MySQL侧把表结构默认字符集设置成utf8mb4。如果是新建库建库语句直接用CREATE DATABASE community_health DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;这样基本杜绝乱码的来源。7.4 预约功能始终保存失败这个排查三个点一是前端提交的数据里“时间”字段是字符串后端实体有没有加DateTimeFormat二是预约状态字段给的值是否在枚举范围内三是数据库表有没有约束和它冲突。一般九成的保存失败都是类型不匹配看后端控制台报错信息比瞎试效率高得多。7.5 登录成功但请求接口报401这种问题多半是前端把Token放在了请求参数而不是Header里或者Token被解析后过期了。后端拦截器可以打印一下对应请求日志确认Header到底有没有传成功。8. 避坑指南与实操心得8.1 别用复杂的代码生成器我见过非常多同学在项目里集成MyBatis-Plus Generator、甚至自定义了一套代码生成前端页面的工具目的就是“快”。结果代码生成后一堆冗余文件放在工程里几百行全是自动生成的逻辑在哪里根本分不清。毕设答辩前你要是从头看一遍自己的代码花的时间比重新写一遍还久。我自己的偏好是手动建表、手动写Mapper XML、手写Service。项目规模不大也就十张表左右手写一遍的好处是你能准确说出每个接口的逻辑。包括MyBatis-Plus这个依赖我建议用它的BaseMapper和ServiceImpl来省掉基础CRUD代码但复杂的统计SQL还是手写XML这样接口实现有轻有重讲起来也有层次。8.2 代码里别堆“试错痕迹”很多人做项目过程中会改需求于是代码里充斥着被注释掉的旧逻辑、调试用的System.out.println、临时写死的假数据。我强烈建议提交前做一次全面清理搜索console.log和System.out全删搜索注释掉的代码大段全删搜索写死的测试值改成从词典表读取。不要觉得这些无关紧要。评审老师一般不会逐行讲代码但一旦评审演示过程中页面报错或者控制台打印出一堆痕迹印象分立刻打折扣。8.3 演示数据要做到“恰到好处的假”做可视化和预约功能没有数据光秃秃肯定不行。建议写一段测试数据SQL脚本一次性往数据库里塞居民档案3050条覆盖不同年龄段包含几条明显的高血压/糖尿病患者预约记录近3个月的状态各异门诊记录跟着预约走至少30条药品库存设置几条低于预警值的记录让预警图表有内容。有了这套脚本演示任何模块都有数据支撑而不是现场临时录数据。脚本建议放在项目根目录的sql/demo_data.sql里这也是加分细节。8.4 答辩准备时怎么讲项目明确讲“解决什么问题”、“用了什么方案”、“最后做出了什么效果”这三个层面不要花大量时间背技术栈概念。评审老师最常用的问题就这几个数据可视化用到了哪些图表数据从哪来JWT的Token过期了怎么办用户重新登录怎么处理多角色权限是怎么控制的预约并发时怎么防止重复慢病统计的SQL是怎么写的这些问题把这个项目真正做一遍之后再对照本文过一遍就基本都能接住。回答的时候多讲场景、讲细节会非常有说服力。结尾最后分享一个自己的实际经验。做这类全栈管理系统最容易踩的坑是“沉迷于搭建忘记了业务”也就是把大量时间花在初始化项目、配置依赖、折腾前端脚手架上等到真正要写业务逻辑的时候发现时间已经不够了。正确的节奏是第一周把环境和数据库表定死第二周做后端CRUD和登录第三周做前端页面和可视化第四周填充数据和打磨细节。加上“演示数据脚本”这个技巧你会发现整个项目的完成度会瞬间上一个台阶。社区医疗服务和可视化管理的组合无论从业务温度还是技术展示度来说都是一个非常适合作为综合训练的项目。写好每一张表、跑通每一个流程这个项目做下来你对前后端分离、权限认证、数据聚合的理解会非常扎实。跨过跑通之前的那道坎后面就是坦途了。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →