ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

微信小程序校友林+SSM全栈开发:从表结构到真机联调

微信小程序校友林+SSM全栈开发:从表结构到真机联调 简介这份基于SSM框架的微信小程序校友林项目源码面向Java后端与微信小程序开发者适合高校课程设计、毕业设计或校友平台二次开发。项目完整覆盖校友身份认证、动态信息发布、活动组织与报名、资源上传下载、论坛互动及网络纪念册等常见场景清晰演示Spring、Spring MVC、MyBatis与小程序前端的数据交互流程。压缩包共1386个文件大小约15.69MB其中143个java文件支撑后端业务150个vue文件构建后台管理界面110个wxml、112个wxss与210个js组成小程序端另含png/svg/jpg图片资源、sql数据库脚本及bat启动配置便于直接导入开发环境运行。目前已有166人学习下载目录按前端、后端、配置分层结构清晰既适合初学者对照源码理解SSM整合思路也适合开发者在此基础上扩展功能模块。整体来看这是一套兼具教学参考与工程实用价值的完整项目。1. 微信小程序校友林 SSM为什么这类项目始终走同一套拆分拿到“微信小程序校友林微信小程序ssm.zip”这个包名时我第一反应是前端一个目录、后端一个目录压缩包命名把一个全栈项目完整交代了。校友林是校园里很常见的微信小程序毕业设计选题学生认养树木、写寄语、看后勤发布的公告管理员在后台维护树位和认捐记录。真正动手的人往往把精力放在小程序页面上结果发现十天能写完页面后端接口和联调却拖了更久。做这类微信小程序和 SSM 组合的项目小程序端只是壳SSM 后端提供的 JSON 接口、登录态和上传下载才算内核。下面按我拿到这种项目后的整理顺序从建模、跑通、联调讲到收尾验证。2. SSM 后端与校友林的业务建模表结构、JSON 契约与登录态校友林不是孤例驾校模拟考试、健身打卡、校园跑腿、阿尔兹海默症语音辅助系统这些基于 SSM 的微信小程序毕业设计换业务不换骨架。把业务收敛清楚后端代码量其实很可控真正的难点在接口契约和登录态设计。2.1 把“校友林”拆成四张核心表校友林和商城、点餐类业务不一样它没有订单流转核心是“树—人”之间的认养关系。我一般把业务对象收敛成四个树木档案、微信用户、认捐记录、公告。这样既覆盖小程序端展示也能支撑后台管理。字段设计上坚持两个习惯表之间只做逻辑关联不建物理外键方便后续删改用 status 这类数字枚举而不是字符串状态程序里集中定义常量。四张表的关系如下。表名说明关键字段备注t_tree树木档案tree_no, species, region, statusstatus: 0在园 1已认领 2移除t_user微信用户openid, nickname, avatar, tokentoken 由后端生成替代每次调微信接口t_donate认捐记录tree_id, user_id, content, create_time, statusstatus: 0待审核 1通过 2驳回t_notice公告title, content, create_time小程序首页列表和详情共用对应的建表 SQL 如下注意表统一用 utf8mb4避免微信昵称里的特殊字符入库报错。CREATE TABLE t_tree ( id INT PRIMARY KEY AUTO_INCREMENT, tree_no VARCHAR(20) NOT NULL COMMENT 树位编号, species VARCHAR(30) NOT NULL COMMENT 树种, region VARCHAR(50) COMMENT 园区位置, status TINYINT DEFAULT 0 COMMENT 0在园 1已认领 2移除, plant_date DATE COMMENT 种植日期, remark VARCHAR(255) COMMENT 补充说明, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT树木档案表; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) NOT NULL UNIQUE COMMENT 微信openid, nickname VARCHAR(50) COMMENT 昵称, avatar VARCHAR(255) COMMENT 头像地址, token VARCHAR(64) COMMENT 登录下发的token, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT微信用户表; CREATE TABLE t_donate ( id INT PRIMARY KEY AUTO_INCREMENT, tree_id INT NOT NULL COMMENT 树木ID, user_id INT NOT NULL COMMENT 用户ID, content VARCHAR(200) COMMENT 寄语内容, status TINYINT DEFAULT 0 COMMENT 0待审核 1通过 2驳回, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT认捐记录表; CREATE TABLE t_notice ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, content TEXT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT公告表;SQL 里几个点说一下openid 加 UNIQUE因为小程序登录时同一用户会重复换取信息没有唯一约束时靠程序去重容易产生脏数据。status 全部用 TINYINT后台查询列表时直接等于 0/1/2避免“已认领”和“认领中”这类字符串在不同人嘴里说法不一致。t_donate 通过 tree_id 和 user_id 指向另外两张表但不建 FOREIGN KEY因为课程设计和实际交付里经常要改树位编号真外键反而限制操作。2.2 统一 JSON 返回体接口先定契约页面再动小程序端发请求拿到的数据必须每个接口长一个样否则前端要挨个接口处理是否为数组、有没有 total、报错是字符串还是对象。常见做法是先定一个 Result 类把 code、msg、data 固定下来所有 Controller 都返回这个结构。public class ResultT { private Integer code; // 200 成功其他为失败 private String msg; // 可读的错误描述 private T data; // 业务数据分页时放分页对象 public static T ResultT success(T data) { ResultT r new Result(); r.setCode(200); r.setMsg(ok); r.setData(data); return r; } public static T ResultT error(String msg) { ResultT r new Result(); r.setCode(500); r.setMsg(msg); return r; } }Controller 里不要直接返回 List 或 Map统一包一层。以树位列表为例RestController RequestMapping(/api/tree) public class TreeController { GetMapping(/list) public ResultPageResultTree list( RequestParam(defaultValue 1) int page, RequestParam(defaultValue 10) int size, RequestParam(required false) String keyword) { int offset (page - 1) * size; ListTree rows treeService.page(offset, size, keyword); int total treeService.count(keyword); return Result.success(new PageResult(total, rows)); } }PageResult 里放 total 和 rows 两个字段小程序端拿到后直接用 rows 渲染列表用 total 判断有没有更多数据。code 一律用 200 表示成功前端判断条件就只有一个data.code 200。很多联调耗时都耗在“后端这次返回的数组没有 total上次报错是字符串这次是对象”这种不一致上先把返回体统一能省掉大半沟通。提示写 SSM 接口时把返回体字段顺序固定成 code、msg、data前端解构时不容易踩到 undefined。2.3 微信登录code2session 与自己的 token校友林的小程序端登录现在不建议再走 wx.getUserInfo 弹窗授权微信已经调整了获取用户昵称头像的方式昵称头像改用填写能力让用户自己维护。标准的链路是小程序 wx.login 拿到临时 code传给后端后端拿 appid、secret 和 code 请求微信的 jscode2session 接口换取 openid再生成自己的 token 返回给小程序端。public String loginByWx(String code) throws Exception { String url https://api.weixin.qq.com/sns/jscode2session? appid appid secret appSecret js_code code grant_typeauthorization_code; // 用 HttpURLConnection 发起 GET 请求并解析返回的 JSON JSONObject resp httpGetJson(url); if (resp null || resp.containsKey(errcode)) { throw new RuntimeException(微信登录失败: resp); } String openid resp.getString(openid); User user userMapper.findByOpenid(openid); if (user null) { user new User(); user.setOpenid(openid); userMapper.insert(user); } String token UUID.randomUUID().toString().replace(-, ); user.setToken(token); userMapper.updateToken(user); return token; }注意几个参数appid 和 secret 永远放在后端配置文件里不能写进小程序的 config.js否则请求里带的就是“小程序 AppID 小程序 secret”语义完全不同。jscode2session 的 grant_type 固定是 authorization_code。code 一次性有效前端要保证只调用一次 wx.login重复调用第二次会返回 errcode。token 用于小程序后续每个请求的身份识别拦截器里从 Header 取 Authorization 字段再去 t_user 表查一次 openidSSM 项目不引入 Redis 也能应付这个量级的用户。2.4 MyBatis 里最容易出错的三个映射细节校友林常用查询就三类首页公告列表、按园区或树种查树、认捐记录分页。Mapper XML 里最容易写崩的是模糊查询和分页。关键字搜索不能直接写 LIKE %#{keyword}%MyBatis 不会帮你把通配符拼进去要用 CONCAT 拼接。select idpageTree resultTypecom.example.entity.Tree SELECT id, tree_no, species, region, status, plant_date FROM t_tree where if testkeyword ! null and keyword ! AND (species LIKE CONCAT(%, #{keyword}, %) OR region LIKE CONCAT(%, #{keyword}, %)) /if /where ORDER BY tree_no LIMIT #{offset}, #{size} /select这里用 #{} 而不是 ${}是为了防止关键字拼接进 SQL 标签会自动处理第一个条件前面的 AND没人传 keyword 时整段条件不生效。分页用 LIMIT 时Java 层要把页码换算成 offset比如第 2 页每页 10 条offset 就是 10不要直接在 SQL 里传 page。另外结果集字段尽量显式列出来SELECT * 一多后面加字段时既影响前端返回体也容易把不必要的列暴露出去。3. 把 SSM 后端跑通JDK、Tomcat、数据库与小程序本机联调拿到项目最先做的是把环境装齐。SSM 的老项目对 JDK 版本敏感JDK 9 以后反射相关写法会报警告直接装 JDK 8 最省事。我这边常用的搭配表如下。组件常见选择说明JDK8兼容性最好老代码不用改Maven3.6.x和 JDK8 搭配没问题Tomcat8.5支持 Servlet 3.1部署 war 方便MySQL5.7 或 8.0建表 SQL 都能跑Spring5.2.x不是 Spring Boot用 XML 配置MyBatis3.5.xmapper XML 写法稳定Spring 6 和 Spring Boot 3 切到了 Jakarta 命名空间很多 SSM 老代码会报 ClassNotFoundException课程设计里没必要赶这个热度等业务跑通再谈升级。Spring 5 配 MyBatis 3.5 是这类交付件里最常见的组合也最容易在网上搜到对应的问题案例。3.1 jdbc 连接与字符编码先跑通最小配置后端能跑起来的前提是数据库连接和编码过滤器都没问题。先看 jdbc.propertiesjdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/xiaoyoulin?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456driver 用 com.mysql.cj.jdbc.Driver对应 MySQL Connector/J 8 以上老项目里写的 com.mysql.jdbc.Driver 在新驱动下会直接启动失败。url 里的 characterEncoding 必须带着否则存进数据库的中文备注会变成问号。再检查 web.xml 里有没有 CharacterEncodingFilter 过滤器这是 SSM 项目里中文乱码最集中的来源。filter filter-nameencoding/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencoding/filter-name url-pattern/*/url-pattern /filter-mapping很多“接口能通但页面全是乱码”的问题不是后端代码没写对而是这个过滤器没配或者 url-pattern 写成了 /api/。一定要写成 /把静态资源和前端请求都覆盖进去。SpringMVC 配置文件里再开注解驱动和静态资源放行到这里后端接口就能在浏览器里先冒烟了。3.2 小程序端接上本机 ssmconfig.js 与 request 封装前端这块校友林这种页面数量不超过 10 个的应用我不用 uni-app原生微信小程序语法就够了如果用 HBuilderX 跑 uni-app还要多一层编译转换交付时别人接手会多一道门槛。小程序端第一步是统一管理接口地址不要在每个页面里硬编码 http 开头的字符串。单独一个 config.jsconst BASE_URL http://192.168.1.10:8080/xiaoyoulin module.exports { BASE_URL, API: { login: BASE_URL /api/login, treeList: BASE_URL /api/tree/list, donateAdd: BASE_URL /api/donate/add } }为什么开发阶段用局域网 IP 而不是 localhost因为预览和真机调试时代码在小程序里运行手机上的 localhost 指向的是手机自己连不上开发机。BASE_URL 里的 IP 要换成开发机在当前局域网的地址手机和电脑连同一个路由器。再用一层 wx.request 封装把 token 和 contentType 统一带上。const config require(./config.js) function request(url, method, data {}) { return new Promise((resolve, reject) { wx.request({ url, method, data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success: res { if (res.data.code 200) resolve(res.data.data) else reject(new Error(res.data.msg)) }, fail: reject }) }) } module.exports request这里把 token 从 storage 里统一取出放进 Header页面代码里就不用每个请求都写一遍。小程序开发工具里本地是 http 协议默认请求会被拦掉需要在“详情-本地设置-不校验合法域名”打勾这是官方为开发阶段提供的调试开关。正式发布前必须换成微信公众平台配置过的 https 域名和合法证书调试勾选不能带到线上版本。注意BASE_URL 末尾的 /xiaoyoulin 是后端 context-path这个值必须和部署到 Tomcat 后的路径完全一致否则所有请求都 404。3.3 真机联调时最容易卡住的三个环境问题第一手机和开发机不在同一局域网小程序 wx.request 直接报 network error。第二电脑上的访问控制放开了 8080 端口手机仍然连不上先在手机浏览器里用局域网 IP 访问一次后端接口能通再回小程序看。第三登录后 token 没写进 storage后续请求的 Authorization 是空字符串后端拦截器直接返回未登录。这三个问题都不改代码纯粹是环境问题但每天能看到同一批人卡在这些地方。对照排查顺序先确认 IP 能连通再确认 Tomcat 已启动且 context-path 正确最后看小程序开发工具 Console 和 Network 面板里的请求头。4. 联调排错白屏、404、空数据与上传失败的高发点4.1 context-path 与 RequestMapping 对不上导致 404404 是联调第一天出现频率最高的报错。排查路径很固定先看小程序 Network 面板里的完整请求 URL把它复制到浏览器直接访问。浏览器能通说明问题在小程序端拼错了路径浏览器也 404说明后端 context-path、Controller 的类注解、方法注解三者至少有一个对不上。Controller 上写了RequestMapping(/api/tree)方法的 list 写的是GetMapping(/list)而 Tomcat 部署的应用名是 xiaoyoulin那么完整地址是http://IP:8080/xiaoyoulin/api/tree/list。小程序 baseURL 用的是/ssm的话就少了 xiaoyoulin 这一段。我一般把 context-path 直接定成 xiaoyoulinconfig.js 和 Tomcat 配置保持一致避免每个页面都去拼前缀。如果是 Eclipse 或 IDEA 里部署 war 包还要顺手检查 Deployment 标签页里的 Application context光改 server.xml 不一定会同步到 IDE 的部署配置。4.2 返回 500 或数据为空JSON 序列化与 resultType 不匹配接口通了但页面白屏多半是后端返回的数据结构和前端预期不一致。常见两种情况。第一种是实体类里放了关联对象比如 Tree 里又带了一个 ListDonateJSON 序列化时性能差还可能栈溢出这种字段要么用 JSONField(serializefalse) 排除要么单独写一个 VO 类只放页面需要的字段。第二种是 Mapper 里 resultType 写成了 MapMyBatis 会把列名转成 map key小程序端拿 res.data.data.rows 没问题但取 tree_no 时变成 treeNo 或 TREETNO视图就全是空。联调期我在纸上贴了一张速查表问题出现先对号入座。现象先看哪里高频原因404Network 面板里的完整 URLcontext-path 不一致500后端日志堆栈实体循环引用 / resultType 错误空数据格式化后的原始 JSON字段名大小写不一致401请求头 Authorizationtoken 未写入或拦截器放行缺失遇到空数据先把后端接口返回的原始 JSON 复制到任意 json 格式化工具里看 key 到底长什么样比对着代码猜快得多。4.3 登录接口被拦截器拦掉放行规则写漏后端加了登录拦截器之后第一波联调症状是“所有接口都返回未登录包括登录接口本身”。原因基本是拦截器把所有 url 都拦截了登录接口也在拦截范围内形成死锁。拦截器核心逻辑如下public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || token.isEmpty()) { response.setStatus(401); return false; } return true; // 实际项目中这里查出当前用户并放入 request attribute } }注册拦截器时要明确放行规则用 WebMvcConfigurer 而不是已经过时的 WebMvcConfigurerAdapter。Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/api/**) .excludePathPatterns(/api/login, /api/tree/list, /upload/**); } }addPathPatterns 从 /api/** 开始再逐个 exclude 掉不需要登录的接口。千万别写成/**把静态资源也拦进去小程序端只有 token 没放的时候页面根本拿不到图片。排查拦截器问题最快的办法是看响应状态码401 说明放行规则有问题500 才是业务代码本身抛异常。4.4 图片上传了但小程序端打不开上传目录不可访问校友林的后台要传树位照片上传逻辑不管用 CommonsMultipartResolver 还是文件上传工具包最终都会存到一个服务端磁盘路径。问题经常发生在路径上图片存进了项目根目录的 WEB-INF/upload 下Tomcat 不允许外部直接访问 WEB-INF小程序端拿到图片 URL 就是 404。解决方式是单独暴露一个 /upload 静态目录。mvc:default-servlet-handler / mvc:resources mapping/upload/** locationfile:/data/xiaoyoulin/upload/ /location 指向服务器的绝对路径mapping 映射成 URL 前缀。图片路径入库时只存相对路径比如 /upload/tree/2024001.jpg小程序端拼上 baseURL 就能直接显示。老项目里常见的做法是把上传根目录用 properties 配好再在 SpringMVC 配置文件里映射换服务器只改一处配置不用重新打包。4.5 页面能显示但点击没反应事件与数据渲染问题这类问题不全是后端的事。很多是 setData 的 key 和模板里的字段不一致或者列表项的自定义属性写错了。排查时先看 Console 有没有报错再看 Network 里这个页面发起的请求返回是否正常最后检查页面的 bindtap 事件名和 wxml 里的是否一致。微信小程序里 wxml 写事件名用驼峰容易踩坑bindtap 的事件名小写开头更稳列表项数据用>dependency groupIdorg.apache.poi/groupId artifactIdpoi/artifactId version4.1.2/version /dependencyGetMapping(/export) public void exportDonate(HttpServletResponse response) throws IOException { ListDonateRecord list donateService.listAll(); try (Workbook wb new HSSFWorkbook(); OutputStream os response.getOutputStream()) { Sheet sheet wb.createSheet(认捐记录); Row header sheet.createRow(0); header.createCell(0).setCellValue(树位编号); header.createCell(1).setCellValue(寄语内容); for (int i 0; i list.size(); i) { Row row sheet.createRow(i 1); row.createCell(0).setCellValue(list.get(i).getTreeNo()); row.createCell(1).setCellValue(list.get(i).getContent()); } response.setHeader(Content-Disposition, attachment;filename URLEncoder.encode(donate.xls, UTF-8)); wb.write(os); } }HSSFWorkbook 对应 .xls导出量在几万行以内足够字段多了以后再把首行做成加粗样式页面侧只需要一个下载按钮不需要额外的前端插件。中文文件名必须 URLEncoder 处理否则部分浏览器下载时文件名乱码。5.3 自定义导航栏高度与 config 环境切换发布前的收尾核心是把开发用的局域网地址统一换掉。config.js 里的 BASE_URL 指到已备案的 https 域名开发版继续用局域网 IP两份配置用注释分开或者用简单的环境判断自动切换。另一个高频坑是自定义导航栏不要写死 44px 或 64px老机型和新机型的安全区不一样用 wx.getMenuButtonBoundingClientRect 能拿到右上角胶囊按钮的 top 和 height。const menu wx.getMenuButtonBoundingClientRect() const statusBarHeight wx.getSystemInfoSync().statusBarHeight const navHeight (menu.top - statusBarHeight) * 2 menu.heightnavHeight 计算出来的就是自定义导航栏需要占用的高度页面顶部留出这个余量标题再垂直居中刘海屏和小屏手机都能对齐。这个计算式是我处理微信小程序顶部导航栏高度时固定使用的姿势配合 config.js 切换环境整个项目从本机联调到提交审核的流程就齐了。本文还有配套的精品资源点击获取
返回列表