从零开发大学生考试服务助手微信小程序:完整实战记录
Meta Description: 手把手教你开发大学生考试服务助手微信小程序,从需求分析、数据库设计、后端API到小程序前端,完整技术栈实战分享。
前言
作为一个大学生,你是否经常错过重要考试的报名时间?是否在茫茫考试信息中迷失方向?本文将分享我如何从零开始,开发一个完整的大学生考试服务助手微信小程序,帮助同学们及时获取考试信息、管理备考计划。
这篇文章适合:
- 想要开发微信小程序的初学者
- 需要完整项目实战经验的开发者
- 对Spring Boot + 微信小程序技术栈感兴趣的同学
阅读本文,你将学到:
- 完整的需求分析与数据库设计
- Spring Boot后端API开发
- 微信小程序前端实现
- 项目部署与运维经验
一、项目概述
1.1 项目背景
大学生面临各类考试:四六级、计算机二级、教师资格证、考研、公务员等。这些考试报名时间分散、信息来源不一,很容易错过重要节点。
核心痛点:
- 考试信息分散,查找困难
- 报名时间容易遗忘
- 备考计划缺乏系统管理
1.2 功能设计
基于以上痛点,我设计了以下核心功能:
| 考试列表 | 按分类展示各类考试信息 |
| 考试详情 | 报名时间、考试时间、费用等 |
| 考试日历 | 可视化展示考试时间线 |
| 收藏订阅 | 关注感兴趣的考试 |
| 消息提醒 | 考试前自动推送提醒 |
| 证书墙 | 记录已通过的考试 |
| 管理后台 | 管理员维护考试数据 |
1.3 技术选型
后端:
- Spring Boot 2.7.18
- MyBatis-Plus(ORM框架)
- MySQL 8.0(数据库)
- JWT(用户认证)
前端:
- 微信小程序原生框架
- WeChat API(登录、订阅消息)
部署:
- 腾讯云轻量应用服务器
- Nginx(反向代理)
- systemd(服务管理)
二、数据库设计
2.1 ER图概览
项目共设计11张表,核心表关系如下:
user (用户)
├── favorite (收藏)
├── subscription (订阅)
├── user_exam_record (考试记录)
└── view_history (浏览历史)
exam (考试)
├── exam_category (分类)
├── exam_subject (科目)
└── material (备考资料)
2.2 核心表结构
考试表(exam):
| id | BIGINT | 主键 |
| name | VARCHAR(100) | 考试名称 |
| category_id | BIGINT | 分类ID |
| register_start | DATE | 报名开始时间 |
| register_end | DATE | 报名结束时间 |
| exam_date | DATE | 考试日期 |
| fee | DECIMAL | 报名费用 |
| description | TEXT | 考试说明 |
订阅表(subscription):
| id | BIGINT | 主键 |
| user_id | BIGINT | 用户ID |
| exam_id | BIGINT | 考试ID |
| reminder_days | INT | 提前提醒天数 |
| status | INT | 状态 |
2.3 建表SQL示例
CREATE TABLE `exam` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL COMMENT '考试名称',
`category_id` bigint DEFAULT NULL COMMENT '分类ID',
`register_start` date DEFAULT NULL COMMENT '报名开始时间',
`register_end` date DEFAULT NULL COMMENT '报名结束时间',
`exam_date` date DEFAULT NULL COMMENT '考试日期',
`fee` decimal(10,2) DEFAULT NULL COMMENT '报名费用',
`description` text COMMENT '考试说明',
`icon` varchar(255) DEFAULT NULL COMMENT '图标',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`),
KEY `idx_exam_date` (`exam_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
三、后端API开发
3.1 项目结构
backend/
├── src/main/java/com/examhelper/
│ ├── controller/ # 控制器层(10个)
│ ├── service/ # 服务层
│ ├── mapper/ # 数据访问层
│ ├── entity/ # 实体类
│ ├── config/ # 配置类
│ ├── task/ # 定时任务
│ └── common/ # 通用类
└── src/main/resources/
└── application.yml # 配置文件
3.2 核心Controller示例
考试控制器:
@RestController
@RequestMapping("/api/exam")
@RequiredArgsConstructor
public class ExamController {
private final ExamService examService;
@GetMapping("/list")
public Result<Page<ExamVO>> list(
@RequestParam(required = false) Long categoryId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
return examService.getExamList(categoryId, page, size);
}
@GetMapping("/{id}")
public Result<ExamDetailVO> detail(@PathVariable Long id) {
return examService.getExamDetail(id);
}
}
3.3 用户认证实现
使用JWT实现微信登录:
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
@Value("${wechat.appid}")
private String appId;
@Value("${wechat.secret}")
private String secret;
@Override
public Result<LoginVO> login(String code) {
// 1. 调用微信API获取openid
String url = String.format(
"https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
appId, secret, code);
String response = HttpUtil.get(url);
JSONObject json = JSON.parseObject(response);
String openid = json.getString("openid");
// 2. 查询或创建用户
User user = userMapper.selectByOpenid(openid);
if (user == null) {
user = new User();
user.setOpenid(openid);
user.setCreateTime(LocalDateTime.now());
userMapper.insert(user);
}
// 3. 生成JWT Token
String token = JwtUtil.createToken(user.getId());
return Result.success(new LoginVO(token, user));
}
}
3.4 定时提醒任务
@Component
@RequiredArgsConstructor
public class SubscriptionReminderTask {
private final SubscriptionMapper subscriptionMapper;
@Scheduled(cron = "0 0 8 * * ?") // 每天早上8点执行
public void sendReminders() {
// 查询需要提醒的订阅
List<Subscription> subscriptions = subscriptionMapper.selectNeedReminder();
for (Subscription sub : subscriptions) {
// 发送订阅消息
wechatService.sendSubscribeMessage(sub);
}
}
}
四、小程序前端开发
4.1 项目结构
miniprogram/
├── pages/
│ ├── index/ # 首页(考试列表)
│ ├── detail/ # 考试详情
│ ├── calendar/ # 考试日历
│ ├── favorites/ # 我的收藏
│ ├── subscriptions/ # 我的订阅
│ ├── certificates/ # 证书墙
│ ├── history/ # 浏览历史
│ ├── profile/ # 个人中心
│ ├── settings/ # 设置
│ ├── feedback/ # 意见反馈
│ └── about/ # 关于我们
├── utils/
│ └── request.js # 封装请求
├── images/ # 图标资源
├── app.js
├── app.json
└── app.wxss
4.2 封装请求工具
// utils/request.js
const BASE_URL = 'http://你的服务器ip/api'
function request(options) {
const token = wx.getStorageSync('token')
return new Promise((resolve, reject) => {
wx.request({
url: BASE_URL + options.url,
method: options.method || 'GET',
data: options.data,
header: {
'Content-Type': 'application/json',
'Authorization': token ? `Bearer ${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: reject
})
})
}
module.exports = { request }
4.3 首页实现
// pages/index/index.js
const { request } = require('../../utils/request')
Page({
data: {
examList: [],
categories: [],
currentCategory: null,
page: 1,
loading: false
},
onLoad() {
this.loadCategories()
this.loadExams()
},
async loadExams() {
if (this.data.loading) return
this.setData({ loading: true })
try {
const data = await request({
url: '/exam/list',
data: {
categoryId: this.data.currentCategory,
page: this.data.page,
size: 10
}
})
this.setData({
examList: this.data.page === 1
? data.records
: […this.data.examList, …data.records],
page: this.data.page + 1,
loading: false
})
} catch (e) {
this.setData({ loading: false })
}
},
onCategoryChange(e) {
this.setData({
currentCategory: e.detail.id,
page: 1,
examList: []
})
this.loadExams()
}
})
4.4 收藏功能实现
// pages/detail/detail.js
Page({
data: {
exam: null,
isFavorite: false
},
async toggleFavorite() {
try {
if (this.data.isFavorite) {
await request({
url: `/favorite/remove/${this.data.exam.id}`,
method: 'DELETE'
})
this.setData({ isFavorite: false })
wx.showToast({ title: '已取消收藏', icon: 'success' })
} else {
await request({
url: `/favorite/add/${this.data.exam.id}`,
method: 'POST'
})
this.setData({ isFavorite: true })
wx.showToast({ title: '收藏成功', icon: 'success' })
}
} catch (e) {
console.error(e)
}
}
})
五、部署与运维
5.1 服务器配置
服务器信息:
- 腾讯云轻量应用服务器
- 配置:2核2G
- 系统:Ubuntu 22.04
5.2 Nginx配置
server {
listen 80;
server_name 你的服务器ip;
# 后端API
location /api/ {
proxy_pass http://127.0.0.1:8081/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 管理后台
location /admin/ {
alias /www/wwwroot/exam-helper/admin/;
index index.html;
}
}
5.3 systemd服务配置
# /etc/systemd/system/exam-helper.service
[Unit]
Description=Exam Helper Backend
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/www/wwwroot/exam-helper/backend
ExecStart=/usr/bin/java -jar target/exam-helper-1.0.0.jar
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
启动服务:
systemctl daemon-reload
systemctl enable exam-helper
systemctl start exam-helper
六、踩坑经验总结
6.1 内存优化
服务器只有2G内存,运行Java服务容易OOM。解决方案:
java -Xmx512m -Xms256m -jar app.jar
systemctl stop docker
systemctl disable multipathd
6.2 微信登录问题
问题: AppID不正确导致登录失败
解决: 确保project.config.json中的appid与微信公众平台一致
6.3 负数倒计时
问题: 考试已过但倒计时显示负数
解决: 在计算倒计时时检查是否为负数:
int countdown = (int) ChronoUnit.DAYS.between(LocalDate.now(), exam.getExamDate());
if (countdown < 0) countdown = 0;
七、项目成果
7.1 功能清单
| 考试列表 | ✅ |
| 考试详情 | ✅ |
| 考试日历 | ✅ |
| 收藏功能 | ✅ |
| 订阅提醒 | ✅ |
| 证书墙 | ✅ |
| 浏览历史 | ✅ |
| 意见反馈 | ✅ |
| 管理后台 | ✅ |
7.2 技术亮点
八、总结与展望
这个项目从需求分析到上线部署,历时约2周时间。通过这个项目,我深入学习了:
未来规划:
- 添加考试资讯爬虫,自动更新考试信息
- 引入AI推荐,根据用户画像推荐考试
- 开发刷题功能,提供备考练习
FAQ
Q: 这个项目需要多长时间开发?
A: 如果有基础,大约1-2周可以完成核心功能。新手可能需要3-4周。
Q: 服务器最低配置要求是什么?
A: 建议2核2G以上,内存太小需要优化JVM参数。
Q: 如何获取微信小程序AppID?
A: 登录微信公众平台,注册小程序账号后即可获取。
Q: 后端可以用其他语言吗?
A: 可以,Node.js、Python、Go都可以,API接口设计是一样的。
关于作者
一个热爱技术的开发者,专注于全栈开发和AI应用。如果你觉得这篇文章有帮助,欢迎点赞收藏!
项目源码:暂不支持
在线体验:暂不支持
关键词: 微信小程序, Spring Boot, 考试助手, 全栈开发, 项目实战



