在高校教育管理领域,实践活动管理一直是个复杂而关键的环节。传统的纸质记录和分散的Excel表格管理方式,不仅效率低下,还容易导致数据不一致和统计困难。针对这一痛点,我们设计开发了一套基于SSM框架的实践活动管理平台,实现了从活动发布、报名审核到成果评定的全流程数字化管理。
系统架构与技术栈
该平台采用经典的SSM(Spring + SpringMVC + MyBatis)三层架构,结合Maven进行项目依赖管理,前端使用JSP渲染页面,数据库选用MySQL 5.7版本。
技术栈配置示例:
<!-- pom.xml 核心依赖配置 -->
<dependencies>
<!-- Spring核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.18</version>
</dependency>
<!-- MyBatis整合Spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!-- JSP支持 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
Spring框架作为IoC容器,管理着所有的业务Bean对象,通过注解驱动的方式实现依赖注入。SpringMVC负责Web请求的分发和处理,采用基于注解的控制器设计,使代码更加简洁明了。MyBatis作为持久层框架,通过XML映射文件实现对象关系映射,提供了灵活的SQL编写能力。
数据库设计亮点
用户权限分离设计
系统采用基于角色的访问控制模型,通过t_user表统一管理所有用户的登录凭证,而具体的角色信息(学生、教师)则分别存储在t_student和t_teacher表中,通过外键关联实现数据的一致性。
-- 用户表核心字段设计
CREATE TABLE `t_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`u_username` varchar(255) DEFAULT NULL COMMENT '用户名',
`u_password` varchar(255) DEFAULT NULL COMMENT '密码',
`u_type` varchar(255) DEFAULT NULL COMMENT '类型(student/teacher/admin)',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`u_username`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
这种设计实现了用户信息的统一认证和角色信息的分离存储,既保证了系统安全,又提供了良好的扩展性。当需要新增角色类型时,只需创建对应的扩展表并维护外键关系即可。
实践活动关联模型
t_shijiancanyu表作为核心的业务关联表,巧妙地将学生、实践活动和评分体系联系在一起:
-- 实践参与表结构优化
CREATE TABLE `t_shijiancanyu` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`t_pf1` decimal(5,2) DEFAULT NULL COMMENT '评分1(优化为数值类型)',
`t_pf2` decimal(5,2) DEFAULT NULL,
`t_pf3` decimal(5,2) DEFAULT NULL,
`t_pjf` decimal(5,2) GENERATED ALWAYS AS ((t_pf1 + t_pf2 + t_pf3)/3) COMMENT '平均分(计算列)',
`shijian_id` int(11) NOT NULL,
`student_id` int(11) NOT NULL,
`participate_status` tinyint(1) DEFAULT '0' COMMENT '参与状态(0-报名中,1-已参与,2-已完结)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_student_shijian` (`student_id`,`shijian_id`),
KEY `idx_shijian_status` (`shijian_id`,`participate_status`),
CONSTRAINT `fk_shijian` FOREIGN KEY (`shijian_id`) REFERENCES `t_shijian` (`id`),
CONSTRAINT `fk_student` FOREIGN KEY (`student_id`) REFERENCES `t_student` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这种设计支持一个学生参与多个实践活动,一个实践活动有多个学生参与的多对多关系。通过计算列自动维护平均分,确保了数据的准确性和一致性。
核心功能实现
1. 实践活动报名管理
系统实现了完整的实践活动报名流程,包括活动发布、学生报名、教师审核等功能。
控制器实现代码:
@Controller
@RequestMapping("/Shijiancanyu")
public class ShijiancanyuController {
@Autowired
private ShijiancanyuMapper shijiancanyuMapper;
@Autowired
private ShijianMapper shijianMapper;
@Autowired
private StudentMapper studentMapper;
/**
* 初始化报名页面,加载可选实践活动和学生列表
*/
@RequestMapping("/initUtil.do")
public String initUtil(HttpServletRequest request, Model model) {
// 获取所有可报名的实践活动
List<Shijian> activeShijians = shijianMapper.getActiveShijians();
model.addAttribute("listShijian", activeShijians);
// 获取所有学生信息(用于管理员操作)
List<Student> allStudents = studentMapper.getObjectList(null, null);
model.addAttribute("listStudent", allStudents);
return "Shijiancanyu/saveOrUpdate";
}
/**
* 学生报名实践活动
*/
@RequestMapping("/applyShijian.do")
@ResponseBody
public Map<String, Object> applyShijian(@RequestParam Integer shijianId,
HttpSession session) {
Map<String, Object> result = new HashMap<>();
try {
// 从session中获取当前登录学生信息
Student currentStudent = (Student) session.getAttribute("currentStudent");
if (currentStudent == null) {
result.put("success", false);
result.put("message", "请先登录");
return result;
}
// 检查是否已报名
Shijiancanyu existing = shijiancanyuMapper
.checkExistingApplication(shijianId, currentStudent.getId());
if (existing != null) {
result.put("success", false);
result.put("message", "您已报名该实践活动");
return result;
}
// 创建新的报名记录
Shijiancanyu application = new Shijiancanyu();
application.setShijianId(shijianId);
application.setStudentId(currentStudent.getId());
application.setParticipateStatus(0); // 报名中
shijiancanyuMapper.insertObject(application);
result.put("success", true);
result.put("message", "报名成功,等待审核");
} catch (Exception e) {
result.put("success", false);
result.put("message", "报名失败:" + e.getMessage());
}
return result;
}
}

2. 分页查询与数据检索
系统实现了高效的分页查询机制,支持按字段搜索和分页显示:
/**
* 分页查询实践参与记录
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@RequestMapping("/getAllUtil.do")
public String getAllUtil(HttpServletRequest request, Model model) {
// 获取查询条件
String field = request.getParameter("field");
String fieldValue = request.getParameter("fieldValue");
// 处理中文编码
try {
if (fieldValue != null) {
fieldValue = new String(fieldValue.getBytes("UTF-8"), "utf-8");
}
} catch (Exception e) {
// 编码处理异常
}
// 分页参数处理
String pageNo = request.getParameter("pageModel.currentPageNo");
int currentPageNo = 1;
try {
currentPageNo = Integer.parseInt(pageNo);
} catch (Exception e) {
// 使用默认页码
}
// 执行查询
List<Shijiancanyu> list = shijiancanyuMapper.getObjectList(field, fieldValue);
// 构建分页模型
PageModel pageModel = new PageModel();
pageModel = pageModel.getUtilByController(list, currentPageNo);
// 返回结果
model.addAttribute("pageModel", pageModel);
model.addAttribute("fieldValue", fieldValue);
model.addAttribute("field", field);
return "Shijiancanyu/find";
}
/**
* 自定义分页工具类
*/
public class PageModel {
private int currentPageNo = 1; // 当前页码
private int totalCount; // 总记录数
private int pageSize = 10; // 页面大小
private List<?> list; // 当前页数据
public PageModel getUtilByController(List list, int currentPageNo) {
PageModel pageModel = new PageModel();
pageModel.setTotalCount(list.size());
pageModel.setCurrentPageNo(currentPageNo);
// 计算分页数据
int fromIndex = (currentPageNo - 1) * pageSize;
int toIndex = Math.min(fromIndex + pageSize, list.size());
if (fromIndex <= toIndex) {
pageModel.setList(list.subList(fromIndex, toIndex));
} else {
pageModel.setList(new ArrayList<>());
}
return pageModel;
}
// getter和setter方法
public int getCurrentPageNo() { return currentPageNo; }
public void setCurrentPageNo(int currentPageNo) { this.currentPageNo = currentPageNo; }
public int getTotalCount() { return totalCount; }
public void setTotalCount(int totalCount) { this.totalCount = totalCount; }
public int getPageSize() { return pageSize; }
public void setPageSize(int pageSize) { this.pageSize = pageSize; }
public List<?> getList() { return list; }
public void setList(List<?> list) { this.list = list; }
}
3. 批量操作与事务管理
系统提供了批量删除等管理功能,确保数据操作的原子性:
/**
* 批量删除实践参与记录
*/
@RequestMapping("/deleteManyUtil.do")
public String deleteManyUtil(HttpServletRequest request, User util, Model model) {
String ids[] = request.getParameterValues("id");
if (ids != null && ids.length > 0) {
try {
for (String id : ids) {
if (id != null && !id.trim().equals("")) {
shijiancanyuMapper.deleteObject(Integer.parseInt(id));
}
}
} catch (Exception e) {
// 记录日志并处理异常
logger.error("批量删除失败", e);
}
}
return this.getAllUtil(request, model);
}
/**
* 评分更新服务,使用Spring事务管理
*/
@Service
@Transactional
public class ScoreService {
@Autowired
private ShijiancanyuMapper shijiancanyuMapper;
/**
* 更新学生实践评分
*/
public boolean updateStudentScores(Integer participationId,
BigDecimal score1,
BigDecimal score2,
BigDecimal score3) {
try {
Shijiancanyu participation = shijiancanyuMapper.selectObject(participationId);
if (participation == null) {
return false;
}
// 更新单项评分
participation.setTPf1(score1);
participation.setTPf2(score2);
participation.setTPf3(score3);
// 计算平均分
BigDecimal avgScore = score1.add(score2).add(score3)
.divide(new BigDecimal(3), 2, RoundingMode.HALF_UP);
participation.setTPjf(avgScore);
// 更新状态为已评分
participation.setParticipateStatus(2);
shijiancanyuMapper.updateObject(participation);
return true;
} catch (Exception e) {
// 事务会自动回滚
throw new RuntimeException("评分更新失败", e);
}
}
}

4. 数据模型设计
系统的实体类设计充分体现了面向对象的思想:
/**
* 实践参与实体类
*/
public class Shijiancanyu {
private Integer id;
private BigDecimal tPf1; // 评分1
private BigDecimal tPf2; // 评分2
private BigDecimal tPf3; // 评分3
private BigDecimal tPjf; // 平均分
private String tBz; // 备注
private Integer shijianId; // 实践ID
private Integer studentId; // 学生ID
private Integer participateStatus; // 参与状态
// 关联对象
private Shijian shijian;
private Student student;
// getter和setter方法
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public BigDecimal getTPf1() { return tPf1; }
public void setTPf1(BigDecimal tPf1) { this.tPf1 = tPf1; }
// ... 其他getter/setter方法
/**
* 计算平均分
*/
public void calculateAverage() {
if (tPf1 != null && tPf2 != null && tPf3 != null) {
this.tPjf = tPf1.add(tPf2).add(tPf3)
.divide(new BigDecimal(3), 2, RoundingMode.HALF_UP);
}
}
}
/**
* 学生信息实体类
*/
public class Student {
private Integer id;
private String TXuehao; // 学号
private String TName; // 姓名
private String TBanji; // 班级
private String TNianji; // 年级
private String TLianxi; // 联系方式
private String TBz; // 备注
private Integer userId; // 用户ID
private User user; // 关联用户信息
// getter和setter方法
}
功能展望与优化方向
1. 引入Redis缓存优化
当前系统的查询性能可以通过引入Redis缓存来大幅提升:
@Service
public class ShijianCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String CACHE_KEY = "shijian:active:list";
private static final long CACHE_EXPIRE = 3600; // 1小时
/**
* 获取缓存中的实践活动列表
*/
@SuppressWarnings("unchecked")
public List<Shijian> getActiveShijiansFromCache() {
try {
Object cached = redisTemplate.opsForValue().get(CACHE_KEY);
if (cached != null) {
return (List<Shijian>) cached;
}
} catch (Exception e) {
// 缓存异常,降级到数据库查询
}
return null;
}
/**
* 更新缓存
*/
public void cacheActiveShijians(List<Shijian> shijians) {
try {
redisTemplate.opsForValue().set(CACHE_KEY, shijians,
CACHE_EXPIRE, TimeUnit.SECONDS);
} catch (Exception e) {
// 记录日志,但不影响主流程
}
}
}
2. 微服务架构改造
将单体应用拆分为微服务架构:
# docker-compose.yml 微服务部署配置
version: '3.8'
services:
user-service:
image: practice-platform/user-service:latest
ports:
- "8081:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_URL=jdbc:mysql://mysql:3306/practice_user
activity-service:
image: practice-platform/activity-service:latest
ports:
- "8082:8080"
depends_on:
- user-service
- redis
gateway-service:
image: practice-platform/gateway-service:latest
ports:
- "80:80"
depends_on:
- user-service
- activity-service
3. 移动端适配与PWA支持
通过响应式设计和PWA技术提升移动端体验:
<!-- 响应式布局示例 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<div class="container-fluid">
<div class="row">
<div class="col-12 col-md-8">
<!-- 移动端优先的实践活动列表 -->
<div class="activity-card" v-for="activity in activities" :key="activity.id">
<h5>{{ activity.name }}</h5>
<p class="text-muted">{{ activity.date }} | {{ activity.location }}</p>
<button class="btn btn-primary btn-sm"
@click="applyActivity(activity.id)"
:disabled="activity.applied">
{{ activity.applied ? '已报名' : '立即报名' }}
</button>
</div>
</div>
</div>
</div>
4. 实时通知系统
集成WebSocket实现实时消息推送:
@ServerEndpoint("/notifications")
@Component
public class NotificationEndpoint {
private static Set<Session> sessions = Collections.synchronizedSet(new HashSet<>());
@OnOpen
public void