实验室管理微信小程序+SSM:资产台账方案与核心实现

发布时间:2026/10/29 8:20:51
实验室管理微信小程序+SSM:资产台账方案与核心实现 简介这份资源是面向微信小程序开发者与计算机专业学生的实验室管理系统完整源码包采用微信小程序前端搭配SSMSpring、SpringMVC、MyBatis后端框架可用于课程设计、毕业设计或二次开发学习。压缩包共1216个文件约28.51MB涵盖js、vue、java、json、wxss、wxml等主要类型分别对应小程序逻辑、管理后台页面、后端服务、配置数据与界面样式另有png、svg、jpg等图片资源及sql脚本、bat启动脚本等辅助文件结构完整。目前已有212人学习下载。源码可直接运行读者能借此掌握小程序页面开发与接口调用、SSM框架的分层搭建、用户角色权限设计以及实验室设备、记录、人员等模块的实现思路适合作为全栈入门与项目实战的参考样例。1. 实验室管理微信小程序SSM一套能跑通的资产台账方案长什么样实验室管理这件事真正难的不是写页面而是把「设备台账、耗材库存、预约记录、报废流程」这几本账对齐。很多课题组还在用 Excel 加微信群接龙设备借出去三天没人记得耗材月底盘点永远对不上。这套「实验室管理微信小程序 SSM」的组合本质是用微信小程序做移动端入口用 SSMSpring SpringMVC MyBatis做后端服务把台账数据集中到一张表里谁借、谁还、剩多少扫码就能查。它适合两类人一类是计算机专业做课程设计或毕设的学生需要一套结构完整、能讲清楚分层架构的项目另一类是实验室管理员或课题组负责人想用最低成本把纸质登记换成线上流程。下面按「先立住架构、再动手复现、最后讲坑」的顺序拆开每一步都落到能抄的配置和代码上。2. 为什么选 SSM 做后端、小程序做前端分层与选型理由2.1 SSM 三层结构在实验室管理里的对应关系SSM 不是三个孤立框架而是一条请求的完整链路。Controller 层接小程序的 HTTP 请求Service 层写业务规则比如「同一台设备同一时段只能被一个人预约」Dao 层用 MyBatis 操作 MySQL。放到实验室管理场景里这条链路对应得很清楚Controller接收wx.request发来的 JSON返回统一格式{code, msg, data}。Service处理预约冲突检测、库存扣减、借还状态流转。DaoMyBatis 的 Mapper 接口 XML写设备表、耗材表、预约表的增删改查。选 SSM 而不是 Spring Boot 的理由很实际课程设计和老项目里SSM 的 XML 配置能让你看清每个 Bean 怎么装配、事务怎么切排查问题时不会像 Spring Boot 自动配置那样变成黑匣子。代价是配置量大但实验室管理这种表结构稳定的系统一次配好后面很少动。2.2 小程序端为什么用原生而不是 uniapp热词里 uniapp 打包微信小程序出现频率很高但实验室管理这类工具型小程序我一般建议用原生开发。原因有三点第一原生wx.request、wx.login、wx.chooseImage的调用链最短出问题看官方文档就能定位第二实验室管理不需要同时发 App 和 H5跨端收益为零第三原生包体积小不会触发 uniapp 常见的source size exceed max limit 2mb报错。小程序端页面不多通常五到六个登录页、设备列表页、设备详情/预约页、耗材库存页、我的借还记录页。列表加载更多用onReachBottom配合分页参数这是原生最稳的写法。2.3 数据库表设计的最小集合在动手写代码前先把表定下来否则后面改字段会牵动 Mapper、Service、前端三处。最小可用集合是四张表表名关键字段说明lab_userid, openid, name, rolerole 区分学生/管理员lab_deviceid, name, model, status, stockstatus: 0 可借 1 借出 2 维修lab_consumableid, name, unit, stock, warn_linewarn_line 低于则预警lab_recordid, user_id, target_id, type, create_time, back_timetype 区分设备/耗材openid是小程序用户唯一标识登录时由后端用code换回来存库不要存 session_key 到前端。3. 从零跑通后端SSM 配置与核心接口实现3.1 环境准备与依赖版本后端跑起来需要 JDK 8、Maven 3.6、MySQL 5.7 或 8.0、Tomcat 8.5。依赖用 Maven 管理核心几个坐标如下版本按你本地仓库已有的选不要盲目追新!-- pom.xml 关键依赖 -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.3.20/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 artifactIdfastjson/artifactId version1.2.83/version /dependency逻辑说明spring-webmvc提供 DispatcherServlet 和注解驱动mybatis-spring负责把 SqlSessionFactory 交给 Spring 管理fastjson用于接口返回 JSON 序列化。参数上MySQL 8 的驱动类要写com.mysql.cj.jdbc.Driver连接串加serverTimezoneAsia/Shanghai否则时间字段会差 8 小时。3.2 登录接口用 code 换 openid 的完整链路小程序端调wx.login()拿到临时 code发给后端后端请求微信接口换 openid。这是整个系统唯一需要外网请求的地方其余接口都在内网完成。// UserController.java 登录接口 RequestMapping(/api/login) ResponseBody public MapString, Object login(RequestBody MapString, String body) { String code body.get(code); // 拼接请求参数appid 和 secret 从配置文件读取 String url https://api.weixin.qq.com/sns/jscode2session ?appid appId secret appSecret js_code code grant_typeauthorization_code; String resp HttpUtil.get(url); // 自行封装 GET 工具 JSONObject json JSON.parseObject(resp); String openid json.getString(openid); // 查库不存在则注册 LabUser user userService.findByOpenid(openid); if (user null) { user userService.register(openid); } MapString, Object result new HashMap(); result.put(code, 0); result.put(userId, user.getId()); result.put(role, user.getRole()); return result; }逻辑说明code只能用一次五分钟过期所以前端拿到后要立刻发请求不要缓存。参数上appid和appSecret必须放在后端配置文件绝不能写进小程序代码否则被人抓包就泄露了。返回的openid是用户唯一标识session_key不要返回给前端。3.3 设备预约接口事务与冲突检测预约是实验室管理里最容易出并发问题的地方。两个人同时点「预约」同一台设备如果不加锁两条记录都会写进去。// DeviceServiceImpl.java 预约核心逻辑 Transactional public MapString, Object reserve(Integer deviceId, Integer userId) { // 1. 行锁查询设备防止并发超借 LabDevice device deviceMapper.selectForUpdate(deviceId); if (device.getStock() 0) { return ResultUtil.fail(库存不足); } // 2. 检查该用户是否已有未归还记录 int count recordMapper.countActive(userId, deviceId); if (count 0) { return ResultUtil.fail(你已借出该设备请先归还); } // 3. 扣库存 写记录 deviceMapper.decreaseStock(deviceId); recordMapper.insert(userId, deviceId, 0); return ResultUtil.success(); }逻辑说明selectForUpdate对应 Mapper XML 里的SELECT ... FOR UPDATE在事务内锁住这一行第二个请求会等待。参数上Transactional默认只对 RuntimeException 回滚如果 Service 里抛的是受检异常要加rollbackFor Exception.class。countActive查的是back_time IS NULL的记录防止一个人重复借同一台设备。3.4 MyBatis Mapper 与 XML 的对应写法Mapper 接口和 XML 必须一一对应namespace 写接口全限定名id 写方法名。这是新手最容易配错的地方。!-- DeviceMapper.xml 片段 -- mapper namespacecom.lab.dao.DeviceMapper select idselectForUpdate resultTypecom.lab.entity.LabDevice SELECT * FROM lab_device WHERE id #{id} FOR UPDATE /select update iddecreaseStock UPDATE lab_device SET stock stock - 1 WHERE id #{id} AND stock 0 /update /mapper逻辑说明decreaseStock的AND stock 0是第二道保险即使行锁失效也不会把库存扣成负数。参数上#{id}是预编译占位符能防 SQL 注入如果要用动态排序字段只能用${}但必须做白名单校验。4. 小程序端落地页面结构、请求封装与列表加载4.1 请求封装与全局配置小程序端不要在每个页面里裸写wx.request封装一层统一处理域名、token 和错误码。// utils/request.js const BASE_URL https://your-domain.com; function request(url, method, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method || GET, data: data || {}, header: { content-type: application/json }, success(res) { if (res.data.code 0) { resolve(res.data); } else { wx.showToast({ title: res.data.msg, icon: none }); reject(res.data); } }, fail(err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); } module.exports { request };逻辑说明BASE_URL必须是 HTTPS 且在小程序后台配置了合法域名否则真机调试会报url not in domain list。参数上header里的content-type要和后端RequestBody对应如果后端用RequestParam接收则要改成application/x-www-form-urlencoded。4.2 设备列表页与加载更多列表页用onLoad拉第一页onReachBottom拉下一页这是原生小程序最稳的分页模式。// pages/device/list.js Page({ data: { list: [], page: 1, hasMore: true }, onLoad() { this.loadData(); }, onReachBottom() { if (this.data.hasMore) this.loadData(); }, loadData() { const that this; request(/api/device/list?page this.data.page, GET).then(res { const newList that.data.list.concat(res.data.records); that.setData({ list: newList, page: that.data.page 1, hasMore: res.data.records.length 10 }); }); } });逻辑说明hasMore判断依据是「本页返回条数是否等于页大小」等于 10 说明可能还有下一页。参数上page从 1 开始后端用LIMIT (page-1)*10, 10分页。注意setData不要传整个大数组数据量大时会有性能问题超过 200 条建议用虚拟列表。4.3 单选框与表单提交借还操作里常用单选框选设备状态原生radio-group的写法要绑定bindchange。!-- pages/record/add.wxml -- radio-group bindchangeonTypeChange labelradio value0 checked/借出/label labelradio value1/归还/label /radio-grouponTypeChange(e) { this.setData({ type: e.detail.value }); }逻辑说明e.detail.value是字符串提交给后端前要parseInt转成数字否则 MyBatis 的Integer参数会报类型不匹配。参数上checked只对第一个生效不要给多个 radio 加 checked。5. 避坑与排查这套组合最容易翻车的五个地方5.1 真机请求失败但开发者工具正常现象开发者工具里接口全通真机预览全部超时。原因开发者工具默认不校验域名真机强制校验。解决在小程序后台「开发设置」里配置 request 合法域名必须是 HTTPS且不能带端口号443 除外。本地调试可以勾选「不校验合法域名」但上线前必须配好。5.2 时间字段差 8 小时现象数据库存的create_time和实际时间差 8 小时。原因MySQL 8 的时区默认是 UTCJDBC 连接串没指定时区。解决连接串加serverTimezoneAsia/Shanghai同时确认 MySQL 服务器time_zone参数。如果还不对检查实体类字段类型是不是java.util.Date建议统一用java.time.LocalDateTime。5.3 预约并发导致库存扣成负数现象压测时两台设备被三个人同时预约库存变成 -1。原因Service 方法没加事务或者加了但没生效同类内部调用不走代理。解决确认Transactional加在 public 方法上且调用方是通过 Spring 注入的 Bean 调用不是new出来的对象。数据库层面给stock字段加UNSIGNED约束作为最后防线。5.4 小程序包体积超 2MB 无法上传现象上传时提示source size exceed max limit 2mb。原因图片、字体、第三方库没压缩。解决图片全部走 CDN不要放本地uni-app项目要开分包原生项目检查node_modules是否被打进包。实验室管理这类工具主包控制在 1MB 以内完全可行。5.5 MyBatis 返回字段为 null现象查询结果里某些字段是 null但数据库明明有值。原因实体类属性名和表字段名不一致且没开驼峰映射。解决在mybatis-config.xml里加setting namemapUnderscoreToCamelCase valuetrue/或者手写resultMap做映射。字段名create_time对应属性createTime开了驼峰就自动匹配。6. 进阶技巧用拦截器做权限校验与操作日志6.1 拦截器区分学生和管理员实验室管理里学生只能借还和查自己的记录管理员才能增删设备和改库存。用 SpringMVC 拦截器统一拦截比在每个 Controller 里写 if 判断干净得多。// AuthInterceptor.java public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception { String uri req.getRequestURI(); // 管理端接口统一前缀 /api/admin if (uri.startsWith(/api/admin)) { String role (String) req.getSession().getAttribute(role); if (!admin.equals(role)) { resp.setStatus(403); resp.getWriter().write({\code\:403,\msg\:\无权限\}); return false; } } return true; } }逻辑说明preHandle返回 false 会中断请求所以要把响应体写完整。参数上req.getSession()依赖服务端 session小程序端要在登录后把JSESSIONID存到 storage每次请求带上。如果不想用 session可以换成 JWT在 header 里传 token拦截器解析 token 取 role。6.2 操作日志表与 AOP 切面借还记录本身就是一种日志但管理员改库存、删设备这些操作也需要留痕。用 Spring AOP 切 Service 层把关键操作写进lab_log表。// LogAspect.java Aspect Component public class LogAspect { Autowired private LogMapper logMapper; AfterReturning(execution(* com.lab.service.*.update*(..)) || execution(* com.lab.service.*.delete*(..))) public void recordLog(JoinPoint jp) { String method jp.getSignature().getName(); Object[] args jp.getArgs(); LabLog log new LabLog(); log.setAction(method); log.setParams(JSON.toJSONString(args)); log.setCreateTime(LocalDateTime.now()); logMapper.insert(log); } }逻辑说明切点表达式匹配update和delete开头的方法AfterReturning保证只在方法成功返回后记录抛异常不记。参数上JSON.toJSONString(args)可能包含敏感字段实际项目里要做脱敏比如把密码字段替换成***。6.3 验证方法用 Postman 和开发者工具交叉验证后端接口写完先用 Postman 跑一遍确认返回格式和状态码。然后在开发者工具的 Network 面板看小程序发出的请求对比两边的参数是否一致。最容易出问题的是content-type和参数位置Postman 里用raw JSON对应RequestBody用form-data对应RequestParam两边必须一致。真机测试时打开 vConsole能看到完整的请求日志比盲猜快得多。我自己的习惯是每加一个接口先在 Postman 里存一个用例改完代码跑一遍确认没破坏旧接口再往下写。这套 SSM 小程序的组合配置阶段最烦但一旦跑通后面加功能就是复制粘贴改字段。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询