微信小程序+SSM社区养老服务系统开发毕设实战指南

📅 发布时间:2026/10/4 1:06:34
微信小程序+SSM社区养老服务系统开发毕设实战指南
简介这套毕业设计项目基于微信小程序与SSM框架构建社区养老服务管理系统面向计算机相关专业学生适用于毕业设计、课程设计或工程实训。前端包含微信小程序页面与Vue管理端后端采用Spring、SpringMVC、MyBatis技术栈覆盖老人档案管理、服务预约、健康数据、工单处理等核心模块并配套论文、答辩PPT、开题报告与任务书可完整支持从立项到答辩的流程。压缩包共1709个文件以js、java、vue、wxml、wxss、json等源码和配置为主含png、jpg图片素材、sql数据库脚本及演示视频整包约88.43MB目录按源码、文档、答辩材料分类便于快速定位。源码均经过测试可运行已有63人学习下载适合在此基础上二次开发也可直接作为毕设参考。1. 为什么“微信小程序社区养老服务系统SSM”是毕业设计里的稳妥牌如果你的毕设题目写着“基于微信小程序社区养老服务系统SSM”那么这套组合在本科课题里已经算非常稳的选择前端用微信小程序解决家属和护理员操作门槛低的问题后端用 SSM 处理管理员对工单和老人档案的维护业务链条清晰论文好写答辩也容易让老师听明白。这类课题包通常会把源码、论文、答辩PPT、开题报告和任务书一起打包成 zip 交付但拿到手最常见的困境不是功能不够而是跑不起来、讲不清、答不上追问。这篇笔记会顺着业务建模、数据表、代码闭环和排错清单一条线展开目标是让你拿到任何同类课题包后能在一周内把它真正跑通并讲圆。2. 先把业务边界定下来角色、核心流程与数据表设计这一章不写代码先做一件很多人跳过但最后返工最多的事定业务边界。社区养老服务系统最容易犯的错是功能越加越多最后论文里业务图画不圆答辩被“这个功能谁用、流程怎么闭环”问住。先把角色、流程和数据表定清楚后面所有代码都是往这张骨架上填肉。2.1 用户角色怎么分管理员、护理员、家属的三方视角社区养老系统的使用者不是只有“管理员”和“老人”两方。常见的合理设计是三到四个角色其中老人本人通常不直接操作小程序而是由家属代下单护理员接单执行管理员在后台做审核和统计。管理员负责维护服务项目、录入并更新老人档案、审核订单、指派护理员、查看服务数据统计。护理员在小程序端看到分配给自己的工单执行后标记完成。家属的角色是替老人预约服务、查看服务进度、对已完成的工单做评价。有的系统还会把“老人”单独做成一个受监护档案对象挂在家属账号下而不是一个可登录角色这样更贴近社区养老的真实场景。这个角色划分直接决定后续的表结构和接口设计账号表里放一个 role 字段区分管理员、护理员和家属老人档案表通过家属账号的 user_id 关联工单表再关联到护理员账号。不要把所有功能塞进一个登录用户里否则后面写权限拦截和页面显示时会非常痛苦。我在带这类课题时一般会在开题阶段就把这三类角色的主流程走一遍顺着角色画用例图越早画越省事。2.2 核心业务流程从服务预约到回访完成的闭环社区养老和普通外卖点单系统最大的区别在于服务不是“下单即完成”它中间有审核、指派和执行环节。典型主流程是这样的家属登录后选择服务项目比如助餐、保洁、陪诊、康复护理提交预约时间与地址管理员审核这条预约通过后生成服务工单并指派给某位护理员护理员在小程序端看到待接单的工单确认接单服务完成后点击完成家属可以对完成的服务进行评价管理员再根据评价做回访记录。工单状态在这个流程里逐步流转我建议定义成 0 待审核、1 待服务、2 服务中、3 已完成、4 已取消、5 已回访。很多课题包源码里只写“状态”字段没有状态机说明结果就是代码里各种数字散落自己想改都不知道从哪里改。把状态机画成论文里的一张图再对应到工单表的一个 int 字段既好写代码也好答辩。这里还有一个容易被忽略的点服务时间冲突。同一个老人在同一时间段只能有一个未完结的工单这个校验放到 Service 层做不能只靠前端禁选时间。类似的业务规则在下一章的代码里会体现出来。2.3 数据表设计与关键字段参考社区养老系统的表不用设计太多核心五张表就够了。下面这张表是我常用的一套基础设计既覆盖主流程又不会让开题报告显得臃肿。表名作用关键字段sys_user账号表id, username, password, role, real_name, phone, status, create_timeelder_info老人档案id, user_id(家属账号), name, gender, birthday, id_card, address, health_status, contact_phoneservice_item服务项目表id, name, category, price, duration, description, cover_url, statusservice_order服务工单表id, order_no, elder_id, item_id, appoint_time, address, worker_id, status, remark, evaluation, create_timevisit_record回访记录表id, order_id, visit_time, content, operator_id账号表里的 password 字段建议论文里写“使用 MD5 加盐存储”哪怕课题包源码里是明文你也要在论文的改进点里提一句答辩会显得你懂安全。老人档案表挂在家属账号下也就是 elder_info.user_id 指向 sys_user.id这样家属可以管理多个老人比如同时给父母和岳父岳母下单。工单表里的 order_no 用时间戳加随机数生成作为展示给用户看的单号不要直接用自增 id 当单号。数据库统一用 utf8mb4别用 utf8否则老人姓名里生僻字或表情符号会插入失败。这个坑我在第一次做同类系统时踩过现象是前端提交后接口报错但看日志完全看不出来是编码问题。后续所有表的 create_time 都建议用 datetime不要用 varchar 存时间否则按时间筛选工单时会非常别扭。3. 先把最小闭环跑起来小程序登录、token 与第一个查询接口很多人在这一步翻车后端工程建好了小程序也建好了但两边对不上。问题往往不是某个技术不会而是没有一个“最小闭环”的概念。所谓最小闭环就是不碰任何业务功能先让小程序通过 wx.request 调通后端一个登录接口拿到 token再带着 token 查询当前用户信息。这个闭环通了后续所有功能都是套路。3.1 后端工程骨架Maven 依赖与 Spring/MyBatis 配置后端我建议直接用单模块 Maven 工程结构按 controller、service、mapper、entity、util 分包不要拆多模块毕业设计没必要。pom.xml 里核心依赖如下dependencies dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.3.20/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.13/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.7/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.28/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.16/version /dependency /dependencies版本号只作参考以你本机 JDK 版本和项目实际情况为准。Spring 5.x 配 JDK 8 或 11 都没问题MySQL 驱动 8.x 可以同时兼容 MySQL 5.7 和 8.0。这里没列 Spring AOP 和 Jackson因为 spring-webmvc 已经传递依赖了 JacksonAOP 用到的 spring-aspects 有需要再补。数据库连接配置写在 jdbc.properties 里然后被 Spring 的 XML 读取。常见的坑是 MySQL 8.x 的驱动类和时区参数驱动类是 com.mysql.cj.jdbc.DriverURL 必须带 serverTimezone否则会报 CST 时区错误。jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/community_care?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password123456applicationContext.xml 里配置数据源、SqlSessionFactory 和 Mapper 扫描spring-mvc.xml 配置注解驱动和 Controller 扫描。有两个细节值得注意第一jdbc.url 里的 符号在 XML 中必须转义成 否则 Tomcat 启动时配置解析直接报错第二MyBatis 的 XML Mapper 文件如果放在 resources 目录下要保证 mapper-locations 路径写对我一般写成 classpath:mapper/*.xml。3.2 初始化数据库建表与插入一个管理员账号在 MySQL 里新建数据库然后执行建表语句。为了最小闭环先只建 sys_user 这一张表就够。CREATE DATABASE IF NOT EXISTS community_care DEFAULT CHARACTER SET utf8mb4; USE community_care; CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role VARCHAR(20) NOT NULL, real_name VARCHAR(50), phone VARCHAR(20), status TINYINT DEFAULT 1, create_time DATETIME ); INSERT INTO sys_user (username, password, role, real_name, phone, status, create_time) VALUES (admin, e10adc3949ba59abbe56e057f20f883e, ADMIN, 系统管理员, 13800000000, 1, NOW());这里 password 存的是“123456”的 MD5 值。为什么要提前做这一步后端登录校验直接比对加密后的字符串比明文存库更容易在论文里解释安全设计。如果课题包源码里是明文登录你也建议改成这种形式改动成本很低。3.3 小程序端工程初始化测试号、导航栏与全局配置微信开发者工具里新建项目时AppID 可以选择“测试号”不需要注册小程序账号真机预览时同样能用。项目创建后先确认 app.json 的基础配置导航栏标题和样式在 window 里统一控制。{ pages: [ pages/login/login, pages/index/index ], window: { navigationBarTitleText: 社区养老服务, navigationBarBackgroundColor: #4A90D9, navigationBarTextStyle: white } }如果你要做自定义导航栏也就是把 navigationStyle 设为 custom那就不要写死顶部高度。不同机型胶囊按钮位置不一样iPhone 和 Android 的刘海屏差异很大。常见的做法是拿胶囊按钮的 boundingClientRect 动态计算导航栏高度存到 globalData 里再让每个页面读取。这个点经常出现在微信小程序顶部导航栏高度的搜索话题里实际开发中很多同学在这里反复调样式。app.js 里可以放全局变量比如后端接口的 baseURL。本地联调时建议用局域网 IP而不是 localhost因为真机预览时 localhost 指向的是手机自己不是你的电脑。App({ globalData: { baseURL: http://192.168.1.100:8080, token: } })这里 baseURL 换成你自己电脑的局域网 IP端口要和 Tomcat 的端口一致。注意后面不要带斜杠小程序端封装 request 时再拼接路径。3.4 登录接口与 token 传递的最小闭环代码后端先写一个简单的登录 Controller接收用户名和密码校验通过后生成一个 UUID 作为 token存到一个静态的内存 Map 里。RestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; PostMapping(/login) public ResultUserVO login(RequestBody LoginDTO dto) { User user userService.login(dto.getUsername(), dto.getPassword()); String token UUID.randomUUID().toString().replace(-, ); TokenStore.put(token, user.getId()); UserVO vo new UserVO(); vo.setToken(token); vo.setUsername(user.getUsername()); vo.setRole(user.getRole()); vo.setRealName(user.getRealName()); return Result.success(vo); } }LoginDTO 用 RequestBody 接收 JSON所以前端 wx.request 里要设置 header 的 content-type 为 application/json。TokenStore 是一个静态 ConcurrentHashMapkey 是 tokenvalue 是用户 id这样后续拦截器从 token 反查用户时不需要查库速度也快。这个方案不是生产级方案但作为毕业设计足够讲清楚论文里也能顺带提一句 Redis 的替代方案。小程序端的请求封装是保证后续开发效率的关键。我一般会封装一个 request.js统一处理 baseURL、token 注入和错误提示。const request (url, method, data) { const app getApp(); return new Promise((resolve, reject) { wx.request({ url: app.globalData.baseURL url, method: method, data: data, header: { Content-Type: application/json, token: app.globalData.token }, success: (res) { if (res.data.code 0) { resolve(res.data.data); } else { wx.showToast({ title: res.data.message, icon: none }); reject(res.data); } }, fail: (err) { wx.showToast({ title: 网络请求失败, icon: none }); reject(err); } }); }); };这段封装的逻辑是所有后端返回结构统一为 Result 对象code 为 0 表示成功非 0 直接弹 toast。前端页面只需要关心 data 部分不用每个页面都写一遍失败处理。token 从 header 里传递后端拦截器从 header 读取这也是目前最通用的小程序与 SSM 后端交互方式。我在做这类课题时通常还会在登录成功后把 token 写入 wx.setStorageSync这样重新打开小程序不用重新登录只需要在 app.js 的 onLaunch 里读取并恢复。4. 把服务预约做成能交付的功能Controller、Service、Mapper 三层调用链最小闭环通了之后接下来就是填充一个完整业务功能我会选择“服务预约”来做范例。因为它贯穿三层架构包含事务、校验、动态 SQL 和状态更新是论文里最能体现技术含量的部分。4.1 统一返回体与 Controller 层设计SSM 项目里每个接口都返回同一种结构前端封装才好写。Result 类我通常放在 common 包里包含 code、message、data 三个字段。public class ResultT { private int code; private String message; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.code 0; r.message success; r.data data; return r; } public static T ResultT error(String message) { ResultT r new Result(); r.code 500; r.message message; return r; } }这里看到 Result 的静态方法直接生成返回对象避免每个 Controller 都 new 一次。code 用 0 表示成功500 表示业务失败和 HTTP 状态码区分开因为 HTTP 可能 200 但业务失败。前端封装里已经约定 code 非 0 就提示 message所以后端所有业务异常都必须通过 Result.error 返回而不是直接抛异常给前端。OrderController 的写法要注意路径设计和参数校验。创建预约的接口用 POST路径用 /api/order/create接收的字段包括 elderId、itemId、appointTime、address 和 remark。RestController RequestMapping(/api/order) public class OrderController { Autowired private OrderService orderService; PostMapping(/create) public ResultOrderVO create(RequestBody Valid OrderCreateDTO dto) { return orderService.createOrder(dto); } }这里把业务逻辑全部放到 Service 层Controller 只做参数接收和返回值转发。假如需要权限控制可以在这里加注解或者依赖拦截器校验角色。不要把 SQL 拼接写进 Controller这是分层架构最基本的要求也是论文里可以展开说设计思想的地方。4.2 Service 层事务与业务校验排期冲突怎么拦Service 层是业务规则的集中地。创建订单时至少做三件事校验老人档案存在、校验服务项目处于上架状态、校验同一老人同一时段没有未完结工单。Service public class OrderService { Autowired private ElderInfoMapper elderInfoMapper; Autowired private ServiceItemMapper serviceItemMapper; Autowired private OrderMapper orderMapper; Transactional(rollbackFor Exception.class) public ResultOrderVO createOrder(OrderCreateDTO dto) { ElderInfo elder elderInfoMapper.selectById(dto.getElderId()); if (elder null) { return Result.error(老人档案不存在); } ServiceItem item serviceItemMapper.selectById(dto.getItemId()); if (item null || item.getStatus() ! 1) { return Result.error(服务项目不可预约); } int count orderMapper.countBusyOrder(dto.getElderId(), dto.getAppointTime(), dto.getAppointTime()); if (count 0) { return Result.error(该时段已有预约); } Order order new Order(); // 拼接订单号并插入 order.setOrderNo(generateOrderNo()); order.setElderId(dto.getElderId()); order.setItemId(dto.getItemId()); order.setStatus(0); orderMapper.insert(order); return Result.success(OrderVO.from(order)); } }Transactional 的作用是整个方法要么全部成功要么全部回滚。这里 createOrder 插入订单时依赖前面的校验结果如果插入时报错前面所有已经执行的操作都不留痕。rollbackFor 指定异常触发回滚。时间冲突校验是这类业务里最容易漏掉的地方。countBusyOrder 的 SQL 在 Mapper XML 中实现核心是查同一位老人、同一个预约时间、并且状态不是已取消的订单数量。注意这里只用了等值判断。如果系统要支持更灵活的时间段重叠判断就得把预约时间设计成开始时间和结束时间两个字段。我在做这类毕业设计时通常建议用等值时间即可论文里提一句“支持时间冲突检测”就够了不要过度设计。4.3 Mapper 层动态 SQL 与分页按状态筛选工单Mapper 层要写动态 SQL 的场景特别多比如后台工单列表需要按状态和时间筛选。用 MyBatis 的 和 标签是最常见的做法。select idselectOrderList resultTypecom.demo.entity.Order SELECT * FROM service_order where if teststatus ! null AND status #{status} /if if teststartTime ! null AND appoint_time gt; #{startTime} /if if testendTime ! null AND appoint_time lt; #{endTime} /if /where ORDER BY create_time DESC /select这里有两个细节。第一 和 是 XML 转义不能直接写大于号小于号否则 XML 解析报错。第二 标签会自动去掉第一个 AND所以每个条件都写 AND 是安全的。如果不动态拼 SQL用注解方式写死 SQL 会需要写多个方法动态 SQL 一条语句就解决。分页可以引入 PageHelper配置十分简单。先加 Maven 依赖再在 applicationContext.xml 里配置拦截器插件bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ property nameplugins array bean classcom.github.pagehelper.PageInterceptor property nameproperties value helperDialectmysql reasonabletrue /value /property /bean /array /property /bean使用 PageHelper 的时候只要在 Service 层查询前调用 PageHelper.startPage(pageNum, pageSize)紧接着的第一条 SQL 就会自动带上 limit 分页。这个机制有一个坑startPage 必须紧挨着查询语句中间不能穿插其他查询否则分页会作用到错误的 SQL 上。很多同学遇到“分页不生效”的问题八成是这个原因。4.4 状态机跑起来接单、完成、回访的防重复操作工单从待审核到已完成每一步都是状态更新。最容易出问题的是“重复点击”导致状态被覆盖。比如护理员连续点了两次“接单”第二次操作应该被拦截。后端解决这个问题不需要复杂的锁用一条带条件的 update 就能实现。Update(UPDATE service_order SET status #{targetStatus} WHERE id #{id} AND status #{expectStatus}) int updateStatus(Param(id) Integer id, Param(targetStatus) Integer targetStatus, Param(expectStatus) Integer expectStatus);这条 SQL 的含义是只有当当前状态等于期望状态时才把状态改为目标状态。护理员接单时expectStatus 传 0待审核targetStatus 传 1待服务如果 update 返回的行数是 0说明工单已经被别人接走或状态早就变了Service 层就返回“操作失败请刷新后重试”。这个写法本质上是一种乐观锁不用给数据库加悲观锁也避免并发下状态错乱。论文里甚至可以把这个设计单独拿出来讲名字就叫“基于状态条件的防重复更新机制”。所有状态变更接口都遵循这个模式你会发现系统的逻辑混乱问题少很多。5. 常见问题排查真机预览、日期格式、图片上传与答辩追问这章写的是我在调试这类项目时反复遇到的真实问题。每一条都有现象、原因、解决三个环节你可以直接当成排查手册来用。5.1 开发者工具正常真机预览却请求失败现象微信开发者工具里接口请求都通点击真机预览后页面能打开但所有数据加载不出来控制台提示 request 请求失败。原因分两种一是开发者工具默认勾选了“不校验合法域名”真机环境不会跳过这个校验二是你用的是 localhost 或 127.0.0.1手机根本访问不到你电脑的地址。解决本地联调用局域网 IP并在开发者工具中勾选“不校验合法域名”只对开发版和体验版生效发布前必须在小程序管理后台配置 request 合法域名域名需要是 HTTPS 且备案过的。如果你把系统同时做成了一个 H5 版本还要注意普通链接和业务域名也要在后台配置否则微信里打开 H5 同样受限。5.2 小程序传时间后端收到后变 null 或者报 400现象前端表单里选了 2024-05-01 10:00:00后端 RequestBody 接收时报 HTTP 400或者字段为 null。原因Spring 默认的 JSON 反序列化不认这个时间格式。解决在实体类的日期字段上加 JsonFormat 注解同时把时区指定为 GMT8否则在服务器上会出现 8 小时偏移。JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8) private Date appointTime;另一个容易翻车的位置是 MySQL 驱动对时间类型的处理如果 entity 里用 String 接收时间随后拼到 SQL 里比较那是隐患。建议统一用 Date 类型 JsonFormat 转换。5.3 图片上传成功前端 image 标签却加载不出来现象wx.uploadFile 返回了路径和文件名数据库也存了但小程序端 显示空白。原因通常是后端返回的是一个像 /upload/xxx.jpg 的相对路径而小程序端 src 写成了直接拼这个相对路径没有拼接域名。解决后端把上传文件的保存路径映射成静态资源路径前端统一用 String.format(%s%s, baseURL, url) 拼接完整地址。可以顺手做一个上传目录统一管理工具在 applicationContext.xml 里配置 resource handler 把磁盘目录映射到 /upload/** 路径这样前端拿到的就只是一个相对路径拼接规则全部收敛到一处。5.4 论文查重改稿后图表编号和交叉引用全乱了现象论文里写了“如图 3-2 所示”查重后在 Word 里删改段落图号引文对不上了手动改了几次仍然有漏。原因图表编号是纯手打文本没有使用 Word 的题注和交叉引用功能。解决所有图、表、公式的编号一律用“插入题注”正文引用用“交叉引用”标题用多级列表关联样式。这样只要按 CtrlA 后 F9 更新域所有编号自动纠正。这个习惯在写开题报告时就要建立不要等到查重结束后再改。5.5 答辩被问“为什么用 SSM 不用 Spring Boot”怎么答现象答辩老师看完项目后问了这句支支吾吾答不上来。原因没有提前预设技术选型问题。这不是代码问题而是思路问题。解决回答从三个方面组织。第一课题要求是基于 SSM 框架教学体系和考核目标围绕手写配置和三层架构理解第二SSM 能清晰看到 Spring IoC、SpringMVC 和 MyBatis 三者如何协作避免 Spring Boot 自动配置把关键细节隐藏掉第三承认技术选型可以升级并主动说如果重新做会基于 Spring Boot 重构但业务模型和数据库设计完全复用。这个回答既守住课题要求又展示了你对技术演进有认知。6. 让作品更有说服力的最后一步定时回访提醒、列表加载更多与自检清单如果你的开题报告和任务书里写了“服务回访”和“数据统计”但代码里只是简单的一个表单那工作量看起来明显不足。这一章写三个能快速补进项目、又容易在答辩时展示的增强点。6.1 用 Scheduled 做一个回访提醒任务服务完成后系统应该提醒管理员做回访。Spring 支持在 SSM 项目里开启定时任务。Component public class VisitRemindTask { Autowired private OrderMapper orderMapper; Scheduled(cron 0 0 9 * * ?) public void remind() { ListOrder list orderMapper.selectNeedVisitOrder(); for (Order order : list) { // 生成回访提醒记录可用日志输出便于演示 System.out.println(需要回访的工单 order.getOrderNo()); } } }spring-mvc.xml 里需加上task:annotation-driven/。cron 表达式里固定每天 9 点执行注意服务器时区问题如果部署在境外云主机早上 9 点可能跑到北京时间下午。6.2 小程序端列表加载更多触底刷新与下拉刷新后台工单列表如果一次性返回几十条小程序端体验会差。实现触底加载的标准方法是页面 json 里开启 onReachBottom代码里记录当前页码。onReachBottom() { const nextPage this.data.page 1; this.loadOrderList(nextPage); }同时开启下拉刷新后要调用 wx.stopPullDownRefresh。之前见过一个课题包里用的是“加载更多”按钮而不是触底加载答辩老师问了一句为什么不用触底学生答不上来。如果你准备用 uniapp 重写或打包还要注意小程序包体积限制是 2MB超出后需要通过分包或者压缩图片源码方式处理。这些都是加分项。6.3 数据流自检清单编号检查项1家属能登录并看到老人列表2提交预约后管理员后台看到待审核工单3审核通过后护理员账号能看到工单4护理员接单、完成家属端状态同步变化5完成后生成回访提醒记录这套系统的价值恰恰在角色流转上。以前帮朋友过一遍类似的课题包印象最深的是他工单表的时间字段是 varchar服务顺序靠字符串比较答辩时被问得接不上来。很多坑不是代码写不出来而是配置和约定没对齐。你动手时先把最小闭环跑通再按这张清单走一遍数据流最后把状态机、事务和权限这几个点讲透整套工作量和答辩准备工作就都到位了。希望帮到你。本文还有配套的精品资源点击获取