1. 项目速览这个校友会系统到底做了什么先别急着看代码。拿到这个标题你需要先想清楚一件事校友会系统本质上是一个信息聚合 社交连接 资源撮合的平台而不是一个简单的CRUD管理系统。很多同学做这类系统容易做成管理后台小程序列表展示结果把核心的互动逻辑丢了答辩的时候被老师一问你这个系统解决的核心痛点是什么一下就露馅了。这套基于Spring Boot 微信小程序的校友会系统核心模块拆开看其实就四条线校友信息管理校友注册、身份认证、校友档案维护、校友地图活动管理活动发布、活动报名、签到统计、活动回顾校友企业管理校友企业名录、招聘信息发布校友企业招聘、校友求职互动与权限班级/届别社群、留言互动、管理员审核机制技术上选型是Spring Boot 3.x或2.7.x MyBatis-Plus MySQL 微信小程序原生开发部署用Nginx反向代理服务器用一台2核4G的云主机就够跑了。这篇文章适合什么样的人正在做课程设计、毕业设计或者想自己捣鼓一个完整全栈项目的同学。不管你是想直接参考架构还是想知道每一步为什么这么设计这篇都有。我会把从数据库设计到小程序端请求封装到部署上线的完整链路都过一遍顺便把我踩过的坑扔出来给你避雷。我默认你有一点Java基础和SQL基础但即便是半吊子水平照着这个思路走也能把项目撑起来。2. 核心设计思路为什么Spring Boot是被逼无奈的最佳选择2.1 技术选型背后的真实考量你可能在网上看到过各种技术选型对比什么SSH、SSM、Spring Boot、Spring Cloud其实放到一个课程设计/毕业设计的场景里Spring Boot是那个不会错的答案原因很现实生态成熟不夸张地说国内高校的Java项目十有八九是Spring Boot。你遇到任何报错搜索引擎上都有人踩过同样的坑。这对时间紧张的学生来说是最大的救命稻草。自动配置省心Spring Boot的自动配置帮你把Spring MVC、事务管理、连接池这些原本需要大量XML配置的东西全包了。你只管写业务代码。内置Tomcat打包成jar直接跑不用在服务器上单独装Tomcat再配置虚拟路径这对Linux操作不熟的同学太友好了。和MyBatis-Plus搭配舒服MyBatis-Plus的BaseMapper直接继承就自带单表CRUD写复杂查询再手动撸SQL各取所需。如果说Spring Boot是标准答案那小程序端选择原生开发则是最省精力的路线。有人可能会问为什么不用uni-app或者Taro理由也很简单微信小程序原生框架的文档最全调试工具最顺手而且这个项目里小程序的复杂度根本用不着跨端框架——你就是做校友会这一个场景要那么多write once run everywhere干嘛2.2 系统角色与权限模型得先分清楚谁是谁校友会系统最容易做糊涂的就是权限模型。我见过很多项目把管理员和校友做成两张毫无关联的表然后小程序端乱传参数后台接口不做鉴权直接裸奔——这样答辩时演示一下还好一被问到安全性就哑口无言。我建议的权限设计是这样的角色标识主要权限超级管理员admin全部模块管理、管理员分配、系统配置学院管理员college_admin管理本学院校友、审核活动与招聘信息普通校友alumni浏览信息、报名活动、发布求职/招聘、修改个人档案游客guest仅可浏览公开的校友活动与新闻未登录时数据库层面建议给用户表加一个role字段值为0/1/2分别对应上述角色然后在后端写一个简版Interceptor拦截器根据请求路径判断是否需要登录、是否限定了角色。提示不要想着做太复杂的RBAC基于角色的访问控制模型什么角色表、权限表、角色权限关联表搞四五张表对于课程设计来说过度设计了反而增加答辩时被追问的风险。你要能说清楚为什么这个项目不需要复杂的RBAC——因为角色固定为三类权限边界清晰一个role字段加拦截器就够。2.3 解决的核心痛点不是管理是连接很多同学做校友会系统把大量精力花在校友信息的增删改查上比如校友姓名、性别、入学年份、专业、工作单位然后做了一个标准得不能再标准的CRUD页面。这没错但完全不够。你得想清楚校友会系统的价值在于把失联的校友连接回来把校友资源盘活。所以这个项目真正该花力气的模块是校友注册后的身份认证与资料完整度引导比如引导填写工作单位、所在城市、从事行业活动的发布、传播、报名、签到闭环校友企业的招聘信息发布与校友的求职简历展示注意隐私保护基于届别/学院维度的通讯录检索脱敏显示默认不显示手机号在整个设计答辩里如果你能把这个连接逻辑讲清楚老师会觉得你不是在为了做系统而做系统而是真的在思考业务价值。3. 数据库设计一张图理清所有表和关系3.1 核心表结构规划数据库设计是整个项目的地基地基歪了后面全得返工。我直接给出我验证过的、比较适合展示的表结构方案。建议你建这几张表1. 用户表alumni_userCREATE TABLE alumni_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, openid VARCHAR(64) UNIQUE COMMENT 微信openid, unionid VARCHAR(64) COMMENT 微信unionid可空, username VARCHAR(32) COMMENT 登录名后台管理员用, password VARCHAR(64) COMMENT 密码BCrypt加密, real_name VARCHAR(32) COMMENT 真实姓名, gender TINYINT COMMENT 性别 0未知 1男 2女, avatar VARCHAR(255) COMMENT 头像URL, phone VARCHAR(20) COMMENT 手机号脱敏展示, email VARCHAR(64) COMMENT 邮箱, college_id BIGINT COMMENT 所属学院ID, grade VARCHAR(16) COMMENT 入学年份如2020, major VARCHAR(64) COMMENT 专业, company VARCHAR(128) COMMENT 当前工作单位, position VARCHAR(64) COMMENT 职位, city VARCHAR(64) COMMENT 所在城市, industry VARCHAR(64) COMMENT 所属行业, role TINYINT DEFAULT 0 COMMENT 角色 0校友 1学院管理员 2超级管理员, status TINYINT DEFAULT 0 COMMENT 状态 0待审核 1正常 2禁用, created_time DATETIME COMMENT 注册时间, updated_time DATETIME COMMENT 更新时间 );注意这里两个关键设计openid是微信登录的唯一标识必须加唯一索引status字段用于注册后人工审核才可访问内部信息的场景这个流程在校友会场景非常真实——你不能让任何人注册了就说是某校校友总要有个认证过程。2. 学院表、班级表学院表很简单就是ID、名称、排序、创建时间。校友会系统通常按学院维度组织线下活动所以college_id会被频繁关联查询建议在用户表上建索引。3. 活动表activity 报名表activity_signup活动表字段要有标题、封面图、内容、活动地点、开始时间、结束时间、报名截止时间、报名人数上限、已报名人数、状态草稿/报名中/进行中/已结束、创建人ID、学院ID。这里有个容易被忽略的字段——limit_count和signup_count。4. 企业表alumni_company 招聘信息表recruitment企业表关联校友用户ID表明该企业的联系人是谁。招聘信息表包含岗位名称、岗位类型全职/实习、薪资范围、工作城市、岗位要求、联系方式、发布时间、状态。这里我建议岗位要求和联系方式用TEXT类型薪资范围用VARCHAR因为8k-12k这种字符串用数字不好表达。5. 新闻资讯表news 留言互动表message新闻资讯是校友会内容运营的窗口留言互动则是让校友发出声音的渠道。3.2 为什么加逻辑删除与自动填充这里说两个MyBatis-Plus的实用配置能极大地提升开发效率逻辑删除在application.yml中配置mybatis-plus.global-config.db-config.logic-delete-field: deleted然后给每张表加一个deleted TINYINT DEFAULT 0字段。好处是什么就是删除操作变成UPDATE ... SET deleted 1数据不会真的消失。如果误删了还能恢复。当然对于课程设计来说最大化价值在于你可以在答辩时说为了保证数据可追溯性我设计了逻辑删除——这不是什么高深技术但体现了工程意识。自动填充通过MetaObjectHandler实现created_time和updated_time的自动填充所有插入和更新操作就不用手动set时间字段了。3.3 事务控制的例子活动报名不能超员一个很现实的并发问题活动报名人数上限100人第101个人报名时怎么办最简单的方案是在插入报名记录前先查signup_count是否小于limit_count再更新signup_count signup_count 1。但并发时可能查和更新之间插入了别人的请求导致超员。稳妥一点的做法是把两步操作放进一个Transactional事务方法里并且用UPDATE activity SET signup_count signup_count 1 WHERE id ? AND signup_count limit_count这种乐观锁写法——受影响行数为1说明占座成功为0说明已经满了。这个设计一定要吃透面试官/答辩老师很喜欢问并发场景下怎么防止活动超员。4. 后端核心实现从登录鉴权到业务闭环4.1 微信小程序登录流程与Session维护微信小程序的登录不是你们网页那种用户名密码登录。微信的流程是小程序端调用wx.login()获取一个临时code小程序把code发送给后端后端拿codeappidappsecret调微信接口https://api.weixin.qq.com/sns/jscode2session拿到openid后端用openid作为用户的唯一标识若首次登录则自动注册查库得到用户信息后端生成一个自定义token比如UUID或者JWT返回给小程序端后续请求头带上这个token后端实际上不会直接拿openid去查每张表而是用token换取用户身份。RestController RequestMapping(/api/auth) public class AuthController { Resource private UserService userService; Resource private RedisTemplateString, Object redisTemplate; PostMapping(/wx-login) public Result wxLogin(RequestBody WxLoginRequest request) { // 1. 调用微信接口获取openid String openid wxService.code2Session(request.getCode()); // 2. 查或创建用户 AlumniUser user userService.findOrCreateByOpenid(openid); // 3. 生成token并缓存用户信息 String token UUID.randomUUID().toString().replace(-, ); redisTemplate.opsForValue().set(alumni:token: token, user.getId(), 2, TimeUnit.DAYS); return Result.ok(token); } }注意不要自己硬造加密token使用UUID或者现有JWT库足够了。Redis存储token的意思是让token可以被主动失效——比如管理员禁用用户后踢他下线。如果你不想引入Redis把这个改成一个token表token、user_id、expire_time也行但要记得清掉过期数据。4.2 接口设计规范统一返回体与分页封装前后端分离项目接口返回格式一定要统一否则小程序端解析数据会非常痛苦。建议定义一个统一的Result类Data public class ResultT { private Integer code; // 200成功4xx业务错误500异常 private String message; private T data; }然后在Controller里所有接口都返回Result成功调Result.ok(data)失败调Result.fail(参数错误)。再配一个全局异常处理器RestControllerAdvice统一捕获业务异常和系统异常转换为合适的返回体。这样小程序端只需要统一处理一次响应拿到code ! 200就弹出提示逻辑非常清晰。分页则不推荐手写PageHelper直接使用MyBatis-Plus的Page对象PageActivity page new Page(current, size); LambdaQueryWrapperActivity wrapper new LambdaQueryWrapper(); wrapper.eq(Activity::getStatus, 1) .orderByDesc(Activity::getStartTime); activityService.page(page, wrapper);Page对象返回后直接封装成{ total, records, current, size }给你前端拿这个结构翻页即可。4.3 文件上传头像、活动封面图怎么处理小程序端需要用wx.uploadFile配合后端的文件上传接口来实现图片上传但存储位置很有讲究。两种常见方案本地存储把文件放在服务器的某个目录Nginx配置静态资源映射。适合课程设计白嫖云服务器零成本但要处理上传时路径合法性校验防止任意文件上传漏洞。云存储比如阿里云OSS、腾讯云COS好处是不占服务器磁盘访问快从服务器本地上传用SDK也就几行代码。但云存储通常要实名认证、可能涉及少量费用适合预算充足的同学。如果选择本地存储后端接口大概是PostMapping(/upload) public Result upload(RequestParam(file) MultipartFile file) { // 判断文件名后缀是否合法jpg/png/gif/webp String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); if (!allowExtSet.contains(ext.toLowerCase())) { return Result.fail(文件类型不合法); } // 生成日期目录 UUID新文件名 String dateDir new SimpleDateFormat(yyyyMMdd).format(new Date()); String fileName UUID.randomUUID().toString().replace(-, ) ext; String path /upload/ dateDir / fileName; file.transferTo(new File(System.getProperty(user.dir) path)); return Result.ok(path); // 返回相对路径由前端拼接完整地址 }注意生产环境的服务器你根本没机会用System.getProperty(user.dir)这种相对路径建议把存储路径配置在application.yml里比如file.upload-path/data/alumni/upload方便后续维护。4.4 招聘信息与求职信息的双向匹配招聘模块如果只是做发布列表那有点浪费校友生态的味道。可以加一个内部推荐的维度招聘信息表加一个字段referrer_id表示推荐人即某个校友把岗位推荐给了另一位校友。这样招聘列表页和详情页都可以展示本岗位由某届某专业校友提供增加可信度和互动感。求职信息同理校友可以发布我目前在看机会方向是Java开发期望城市杭州企业方管理员或校友可以看到并联系。所有联系方式不直接展示明文而是通过意向沟通按钮发起由系统转发保证隐私。这个点是不是还挺加分的答辩的时候你说我关注了校友求职的隐私保护需求和信任推荐机制这句话就能展现你超出CRUD的水平。5. 小程序前端从页面结构到请求封装5.1 页面拆分TabBar四个小模块小程序端的TabBar建议这样设计首页轮播图、最新活动、新闻资讯摘要、校友企业推荐活动活动列表、活动筛选按学院/时间、活动详情、报名入口校友圈留言互动列表、发布动态或者叫校友风采我的个人信息、我的报名、我的发布、身份认证入口这四块逻辑刚好对应后端的几个主模块页面结构干净导师一眼就能看出你的系统是完整的。5.2 封装request.js别在每个页面里重复写请求小程序的wx.request是回调风格不封装会写出一堆嵌套地狱。建议封装一个公共请求模块// utils/request.js const BASE_URL https://your-domain.com/api; function request(path, method, data, needAuth true) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL path, method: method, data: data, header: { Content-Type: application/json, Authorization: needAuth ? wx.getStorageSync(token) : }, success(res) { if (res.data.code 200) { 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); } }); }); } module.exports { get: (url, params) request(url, GET, params), post: (url, data) request(url, POST, data) };这样每个页面的调用就变成const { get, post } require(../../utils/request); get(/activity/list, { current: 1, size: 10 }).then(data { this.setData({ list: data.records }); });注意一个细节404和500状态要区分处理。wx.request在HTTP状态码为4xx/5xx时也会走success回调所以res.statusCode不等于200时也要做出业务提示否则用户看到undefined这种报错很莫名其妙。5.3 活动报名的前端体验一个完整的交互闭环报名模块是最能体现交互设计的地方。小程序端应该展示活动卡片封面图、标题、时间、地点、剩余名额点开详情页后状态要明确报名未开始按钮置灰显示报名未开始报名已结束按钮置灰显示报名已结束名额已满按钮置灰显示名额已满可报名显示立即报名已报名显示已报名点进去可以查看报名详情或取消报名取消报名要记得释放名额后端从报名表删除记录同时把活动的signup_count减一。提示前端根据signup_count limit_count判断满员这里只是展示层的判断真正的校验必须放后端。原因很简单——前端可以改你没有理由信任它。5.4 更好的选择统一使用微信生态能力做小程序端时有一点要特别提醒能用微信自带的能力就别自己去实现。头像昵称使用wx.getUserProfile注意微信官方已经调整了规则更推荐头像昵称填写能力手机号使用wx.getPhoneNumber组件获取后端调用接口解密定位城市使用wx.getLocation授权获取消息提醒报名成功后可以通过wx.requestSubscribeMessage引导用户订阅活动开始提醒这些功能不仅让体验更原生也算是在向答辩老师展示你懂小程序生态开发而不是单纯把一个H5套进小程序壳里。6. 部署上线从本地到云服务器的完整操作记录6.1 后端打包与运行Spring Boot项目打成jar包非常简单在项目根目录执行mvn clean package -DskipTests就会在target目录下生成一个alumni-system.jar。在云服务器上安装好JDK和MySQL把jar包传上去然后nohup java -jar alumni-system.jar --spring.profiles.activeprod app.log 21 用nohup让进程在后台持续运行app.log记录日志。第一次启动后记得看日志有没有报错最常见的问题是数据库连不上或者端口被占用。提示如果你用的是Spring Boot 3.x最低需要JDK 17别在服务器上装了8就启动会报UnsupportedClassVersionError这个坑我见过不下五次。6.2 Nginx反向代理与HTTPS小程序上线要求接口必须是HTTPS域名所以如果你的小程序要发布体验版这一步绕不开。Nginx配置的大致骨架server { listen 443 ssl; server_name your-domain.com; ssl_certificate /etc/nginx/cert/xxx.pem; ssl_certificate_key /etc/nginx/cert/xxx.key; location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /upload/ { alias /data/alumni/upload/; expires 7d; } }注意proxy_pass http://127.0.0.1:8080/;这行末尾的斜杠——如果写成proxy_pass http://127.0.0.1:8080;不带斜杠代理到后端时/api这个前缀会被保留而通常后端接口不需要这个前缀就会404。这是Nginx配置里最容易踩的坑。6.3 数据库备份脚本毕设里可能用不上但基础工作还是要做。写一个简单的备份脚本每天凌晨备份一次MySQL数据#!/bin/bash # /root/backup.sh backup_dir/data/backup date_str$(date %Y%m%d%H%M) mysqldump -uroot -p你的密码 alumni_db ${backup_dir}/alumni_db_${date_str}.sql find ${backup_dir} -name *.sql -mtime 7 -exec rm -f {} \;然后用crontab加上自动执行crontab -e 0 2 * * * /root/backup.sh这个脚本的意义不在于展示技术而是让你在答辩时可以理直气壮地说我考虑了数据安全和运维备份方案这是很多学生项目缺失的工程素养。7. 常见问题与排查技巧实录7.1 微信登录报code无效或40029这个错误90%的情况下是appid和secret配置错了或者小程序端使用的code是旧的一个code只能用一次。如果你在开发工具里测试每次重新编译都会生成新的code旧code再调后端接口就会报错。排查思路先确认application.yml里的wx.appid和wx.secret和微信公众平台上的完全一致。再看后端日志里微信接口的实际返回内容——如果返回errcode: 40029直接换一个新的code重试即可。7.2 小程序请求不到本地后端接口微信开发者工具里如果后端跑在localhost:8080小程序默认访问的是开发工具的模拟环境需要在开发工具里勾选不校验合法域名并且把请求地址改成http://127.0.0.1:8080。如果你用的是真机预览手机和电脑必须在同一局域网且地址要改成电脑的局域网IP比如http://192.168.1.100:8080。提示开发阶段如果一直用真机调试建议在后端配置类里写一个CORS跨域配置允许*而不是每次都在小程序开发工具里改设置省去很多麻烦。7.3 数据库连接不上或SQL中文乱码MySQL 8.x连接不上多数情况是驱动版本问题和URL时区问题。Spring Boot 3的配置应该是spring: datasource: url: jdbc:mysql://localhost:3306/alumni_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8allowPublicKeyRetrievaltrue username: root password: xxx driver-class-name: com.mysql.cj.jdbc.DriverserverTimezoneAsia/Shanghai是必须的毕竟MySQL 8默认UTC时区不设置会导致时间字段差8小时。characterEncodingutf8不设置会导致中文乱码。顺便提一句建库时用utf8mb4而不是utf8因为utf8mb4才真正兼容所有Unicode字符包括各种生僻字和emoji。7.4 MyBatis-Plus查询失效或映射错误最常见的问题是实体类字段和数据库字段对不上。MyBatis-Plus默认开启驼峰映射所以Java里的realName能自动对应数据库的real_name但前提是你没有关闭这个配置。另外一个坑是TableField标注的条件没写对或者字段名打错导致SQL直接报Unknown column。排查方法很简单在application.yml打开SQL日志mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl然后去看控制台打印的完整SQL问题基本一眼就能看出来。7.5 图片上传失败或访问不到图片上传失败先看Nginx有没有给/upload/配静态映射以及目录权限是否够。使用Windows本地开发时File.transferTo如果目标目录不存在会直接报错所以务必先mkdirs再transfer。访问不到图片的另一个典型场景是数据库存的是/upload/20250601/xxx.jpg但前端图片地址是域名相对路径拼接。由于开发工具里用的可能是http://localhost:8080而生产环境是https://域名这些地址应该写成一个全局配置常量而不是写死在代码里。8. 写在最后几个让我后悔没早点知道的事这个项目从零到最后跑通我整整折腾了两周半。说实话最耗时间的不是写代码而是调参和排错——Redis配置不对、跨域忘记处理、小程序真机调试白屏、证书过期没发现这些问题每一个都够卡你半天。我现在回头看有几件事真的应该早点做。第一先把接口文档写好再写代码。哪怕只是用表格列一下每个接口的路径、参数、返回结构写代码的效率至少提高30%。因为前后端并行开发时你不必在写后端时时时想着小程序要什么字段也不用写完再一个个页面去对字段名。第二给自己留一个Mock数据工具。系统刚搭好时很多功能是联调不了的比如微信登录没有真机测试、活动报名没有真实数据。这时候自己用SQL脚本生成一批伪装数据——几十个校友、十几个活动、几条招聘信息填进去。这样不仅开发时看着有东西论文截图、答辩演示也好看得多。第三Git提交信息写清楚。课程设计周期短你可能觉得git每次提交随便写个update就行。但等你某天回退代码、或者请求老师帮你排查问题时一眼能看出第几个版本改了什么东西的提交历史能帮你省出一个下午。我在开发到一半时不慎覆盖了活动模块的一个文件花了两个小时才找回就是因为提交信息完全是空的。最后分享一个小技巧在答辩前把自己当成第一次用这个系统的人过一遍完整流程。从游客看到首页到注册成为校友到报名活动、发布招聘信息、再到管理员审核通过——每一步都用真机走一遍保证数据流是通的、按钮不会点了没反应。这比背一百遍PPT有用得多。因为评委最关心的只有一件事这个系统能不能真正跑起来解决用户的问题。你只要把自己的系统用熟问到任何模块都胸有成竹这个毕设就成功了大半。
阅读完成 · 觉得有帮助?