在高校日常管理中,校园活动组织常面临信息传递分散、报名统计繁琐、参与度难以追踪等问题。基于 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"

>

更多推荐