SpringBoot + 微信小程序实战:高校校园微活动系统全栈开发
在高校日常管理中,校园活动组织常面临信息传递分散、报名统计繁琐、参与度难以追踪等问题。基于 SpringBoot 与微信小程序开发的校园微活动系统,能实现 “活动发布 - 报名 - 签到 - 数据统计” 全流程线上化,不仅贴合校园实际需求,更是 Java Web 方向毕业设计的优质选题。本文从程序员视角,完整拆解系统开发全流程,包含后端架构设计、前端页面实现、数据库搭建等核心内容,并提供可直接复用的代码片段与部署方案,助力高效完成毕设开发。
后端架构设计:SpringBoot 三层架构落地
系统后端采用 SpringBoot 2.7.x 构建,遵循 “Controller-Service-Repository” 三层架构,结合 MyBatis-Plus 简化数据操作,Shiro 实现权限控制,通过 RESTful 接口为前端提供服务。核心模块涵盖用户管理、活动管理、报名管理、签到管理,满足校园活动全流程需求。
1. 项目结构与核心配置
合理的项目结构是代码可维护性的基础,后端按功能模块划分包结构,关键配置聚焦数据库连接、接口文档与权限控制。
项目包结构
com.campus.activity
├── config // 配置类(Shiro、Swagger、跨域等)
├── controller // 接口层(接收请求、返回响应)
├── service // 业务逻辑层(核心业务处理)
│ └── impl // 业务逻辑实现类
├── mapper // 数据访问层(数据库操作)
├── entity // 数据库实体类(与表结构映射)
├── dto // 数据传输对象(前后端交互用)
├── vo // 视图对象(接口返回格式化数据)
├── util // 工具类(加密、日期、OSS上传等)
└── CampusActivityApplication.java // 项目启动类
数据库配置(application.yml)
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/campus_activity?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&allowMultiQueries=true
username: root
password: root
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
# MyBatis-Plus配置
mybatis-plus:
mapper-locations: classpath:mapper/*.xml # Mapper XML文件路径
type-aliases-package: com.campus.activity.entity # 实体类别名包
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志(开发环境)
# 自定义配置(活动图片上传路径)
activity:
upload:
base-path: /var/campus/activity/cover/
access-url: http://localhost:8080/cover/
Swagger 接口文档配置
通过 Swagger 自动生成接口文档,方便前后端联调与毕设答辩展示:
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
// 扫描所有控制器包
.apis(RequestHandlerSelectors.basePackage("com.campus.activity.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("校园微活动系统API文档")
.description("用于微信小程序前端调用的后端接口,包含活动、报名、签到等模块")
.version("1.0")
.build();
}
}
2. 核心业务模块实现
以活动管理模块为例,展示从接口定义到业务逻辑的完整实现流程,包含活动发布、列表查询、详情获取等核心功能。
Controller 层(接口定义)
@RestController
@RequestMapping("/api/activity")
@Api(tags = "活动管理接口")
public class ActivityController {
@Autowired
private ActivityService activityService;
@PostMapping
@ApiOperation("发布新活动(仅管理员)")
@RequiresRoles("admin") // Shiro权限控制:仅管理员可调用
public ResultDTO addActivity(@Valid @RequestBody ActivityDTO activityDTO,
@RequestHeader("token") String token) {
// 从token解析发布者ID(用户ID)
Integer publisherId = ShiroUtils.getUserIdFromToken(token);
activityService.createActivity(activityDTO, publisherId);
return ResultDTO.success("活动发布成功");
}
@GetMapping("/list")
@ApiOperation("分页查询活动列表")
public ResultDTO<IPage<ActivityVO>> getActivityList(
@RequestParam(required = false) String title, // 活动标题模糊查询
@RequestParam(required = false) Integer status, // 活动状态(1-未开始 2-进行中 3-已结束)
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
Page<ActivityVO> page = new Page<>(pageNum, pageSize);
IPage<ActivityVO> activityPage = activityService.queryActivityPage(page, title, status);
return ResultDTO.success(activityPage);
}
@GetMapping("/{id}")
@ApiOperation("获取活动详情")
public ResultDTO<ActivityDetailVO> getActivityDetail(@PathVariable Integer id) {
ActivityDetailVO detailVO = activityService.getActivityDetail(id);
return ResultDTO.success(detailVO);
}
@PutMapping("/{id}/status")
@ApiOperation("更新活动状态(取消/结束)")
@RequiresRoles("admin")
public ResultDTO updateActivityStatus(@PathVariable Integer id,
@RequestParam Integer status) {
activityService.updateActivityStatus(id, status);
return ResultDTO.success("活动状态更新成功");
}
}
Service 层(业务逻辑实现)
@Service
public class ActivityServiceImpl implements ActivityService {
@Autowired
private ActivityMapper activityMapper;
@Autowired
private ActivityCategoryMapper categoryMapper;
@Autowired
private FileUploadUtil fileUploadUtil;
@Override
@Transactional // 事务控制:确保业务操作原子性
public void createActivity(ActivityDTO dto, Integer publisherId) {
// 1. 验证活动分类是否存在
ActivityCategory category = categoryMapper.selectById(dto.getCategoryId());
if (category == null) {
throw new BusinessException("活动分类不存在,请选择有效分类");
}
// 2. 处理活动封面图片上传(Base64转文件保存)
String coverUrl = null;
if (StringUtils.isNotBlank(dto.getCoverBase64())) {
// 调用工具类上传图片,返回访问URL
coverUrl = fileUploadUtil.uploadBase64Image(dto.getCoverBase64(), "activity/cover/");
}
// 3. 转换DTO为数据库实体
Activity activity = new Activity();
BeanUtils.copyProperties(dto, activity);
activity.setPublisherId(publisherId);
activity.setCoverUrl(coverUrl);
activity.setStatus(1); // 初始状态:未开始
activity.setSignupCount(0); // 初始报名人数:0
activity.setCreateTime(new Date());
activity.setUpdateTime(new Date());
// 4. 保存活动信息到数据库
activityMapper.insert(activity);
}
@Override
public IPage<ActivityVO> queryActivityPage(Page<ActivityVO> page, String title, Integer status) {
// 调用MyBatis-Plus分页查询
return activityMapper.queryActivityPage(page, title, status);
}
@Override
public ActivityDetailVO getActivityDetail(Integer id) {
// 1. 查询活动基本信息
Activity activity = activityMapper.selectById(id);
if (activity == null) {
throw new BusinessException("活动不存在或已删除");
}
// 2. 关联查询分类名称
ActivityCategory category = categoryMapper.selectById(activity.getCategoryId());
String categoryName = category != null ? category.getName() : "未知分类";
// 3. 转换为详情VO(包含格式化时间、分类名称等)
ActivityDetailVO detailVO = new ActivityDetailVO();
BeanUtils.copyProperties(activity, detailVO);
detailVO.setCategoryName(categoryName);
// 格式化时间(前端展示友好)
detailVO.setStartTimeStr(DateUtil.format(activity.getStartTime(), "yyyy-MM-dd HH:mm"));
detailVO.setEndTimeStr(DateUtil.format(activity.getEndTime(), "yyyy-MM-dd HH:mm"));
detailVO.setSignupStartTimeStr(DateUtil.format(activity.getSignupStartTime(), "yyyy-MM-dd HH:mm"));
detailVO.setSignupEndTimeStr(DateUtil.format(activity.getSignupEndTime(), "yyyy-MM-dd HH:mm"));
return detailVO;
}
// 其他方法实现...
}
微信小程序前端开发:原生框架实战
前端采用微信小程序原生框架,结合 Vant Weapp 组件库快速构建 UI 界面,通过封装的网络请求工具与后端接口交互。核心页面包含活动列表、活动详情、报名表单、个人中心等,覆盖用户全流程操作。
1. 项目结构与工具封装
合理的前端项目结构便于协作与维护,核心工具类聚焦网络请求、登录授权与通用方法。
项目结构
/pages
├── activity // 活动相关页面
│ ├── list // 活动列表页(首页)
│ ├── detail // 活动详情页
│ └── apply // 活动报名页
├── user // 用户相关页面
│ ├── login // 登录授权页
│ ├── center // 个人中心
│ └── my-activity // 我的活动(报名/参与记录)
├── admin // 管理员页面(活动发布/管理)
│ ├── publish // 发布活动页
│ └── manage // 活动管理页
└── common // 公共页面(关于、帮助等)
/utils
├── request.js // 网络请求封装(统一拦截、错误处理)
├── auth.js // 登录授权工具(微信登录、Token管理)
└── util.js // 通用工具(日期格式化、验证等)
/components // 自定义组件(活动卡片、空状态等)
网络请求封装(utils/request.js)
统一处理请求头、Token 携带、错误拦截,减少重复代码:
// 基础请求地址(开发/生产环境可配置)
const baseUrl = "http://localhost:8080/api";
/**
* 封装微信小程序网络请求
* @param {Object} options - 请求配置(url、method、data等)
*/
export const request = (options) => {
return new Promise((resolve, reject) => {
// 1. 获取本地存储的Token(登录后存储)
const token = wx.getStorageSync("token") || "";
// 2. 构造请求参数
const requestParams = {
url: baseUrl + options.url,
method: options.method || "GET",
header: {
"Content-Type": "application/json",
"token": token // 携带Token供后端验证
},
data: options.data || {},
// 3. 请求成功回调
success: (res) => {
const { code, msg, data } = res.data;
// 401:Token过期或未登录,跳转登录页
if (code === 401) {
wx.removeStorageSync("token"); // 清除无效Token
wx.navigateTo({ url: "/pages/user/login/login" });
reject(new Error("登录已过期,请重新登录"));
return;
}
// 非200状态码:业务错误(如参数错误、活动不存在)
if (code !== 200) {
wx.showToast({ title: msg || "操作失败", icon: "none", duration: 2000 });
reject(new Error(msg || "请求失败"));
return;
}
// 成功:返回业务数据
resolve(data);
},
// 4. 请求失败回调(网络错误、超时等)
fail: (err) => {
wx.showToast({ title: "网络异常,请稍后重试", icon: "none", duration: 2000 });
reject(err);
}
};
// 5. 发起微信原生请求
wx.request(requestParams);
});
};
// 简化常用请求方法(GET/POST)
export const get = (url, data) => request({ url, data, method: "GET" });
export const post = (url, data) => request({ url, data, method: "POST" });
export const put = (url, data) => request({ url, data, method: "PUT" });
export const del = (url, data) => request({ url, data, method: "DELETE" });
2. 核心页面实现
以活动列表页和活动详情页为例,展示前端页面的逻辑实现与 UI 渲染,结合 Vant 组件提升开发效率。
活动列表页(pages/activity/list/list.js)
实现下拉刷新、上拉加载、条件筛选等功能:
import { get } from "../../../utils/request";
import { formatTime } from "../../../utils/util";
Page({
data: {
activityList: [], // 活动列表数据
pageNum: 1, // 当前页码
pageSize: 10, // 每页条数
hasMore: true, // 是否还有更多数据
loading: false, // 加载中状态
status: -1, // 活动状态筛选(-1-全部 1-未开始 2-进行中 3-已结束)
keyword: "" // 搜索关键词
},
// 页面加载时初始化数据
onLoad() {
this.loadActivityList();
},
// 加载活动列表(支持下拉刷新、上拉加载)
loadActivityList(isLoadMore = false) {
// 防止重复请求
if (this.data.loading || !this.data.hasMore) return;
this.setData({ loading: true });
const { pageNum, pageSize, status, keyword } = this.data;
// 调用后端接口获取活动列表
get("/activity/list", {
pageNum: isLoadMore ? pageNum + 1 : 1,
pageSize,
status: status === -1 ? null : status,
title: keyword
}).then(res => {
const { records, total } = res;
// 格式化时间(前端展示)
const formattedList = records.map(item => ({
...item,
startTimeStr: formatTime(new Date(item.startTime), "yyyy-MM-dd HH:mm"),
endTimeStr: formatTime(new Date(item.endTime), "yyyy-MM-dd HH:mm")
}));
// 更新页面数据
this.setData({
activityList: isLoadMore ? [...this.data.activityList, ...formattedList] : formattedList,
pageNum: isLoadMore ? pageNum + 1 : 1,
hasMore: (isLoadMore ? pageNum + 1 : 1) * pageSize < total,
loading: false
});
}).catch(err => {
console.error("加载活动列表失败:", err);
this.setData({ loading: false });
});
},
// 下拉刷新(重新加载第一页)
onPullDownRefresh() {
this.loadActivityList(false);
wx.stopPullDownRefresh(); // 停止下拉刷新动画
},
// 上拉加载更多(加载下一页)
onReachBottom() {
this.loadActivityList(true);
},
// 搜索活动(关键词变化时重新加载)
handleSearch(e) {
const keyword = e.detail;
this.setData({ keyword, pageNum: 1, hasMore: true });
this.loadActivityList(false);
},
// 切换活动状态筛选
handleStatusChange(e) {
const status = e.detail;
this.setData({ status, pageNum: 1, hasMore: true });
this.loadActivityList(false);
},
// 跳转到活动详情页
goToDetail(e) {
const activityId = e.currentTarget.dataset.id;
wx.navigateTo({ url: `/pages/activity/detail/detail?id=${activityId}` });
}
});
活动列表页 UI(pages/activity/list/list.wxml)
使用 Vant 组件实现搜索框、筛选栏、活动卡片,提升页面美观度:
<view class="container">
<!-- 搜索框 -->
<van-search
placeholder="搜索活动名称"
bind:search="handleSearch"
class="search-bar"
/>
<!-- 筛选栏 -->
<view class="filter-bar">
<van-segmented-control
value="{{status}}"
bind:change="handleStatusChange"
active-color="#2f54eb"
>
更多推荐


所有评论(0)