1. 项目背景与核心需求
大学生科技竞赛管理系统是当前高校信息化建设中的重要一环。随着各类学科竞赛数量的激增,传统的人工管理方式已经无法满足需求。我去年参与开发的这套系统,正是为了解决某高校创新创业学院面临的几个痛点问题:
- 报名流程繁琐:学生需要填写纸质表格,再由辅导员统一录入
- 信息同步滞后:比赛结果、评审进度无法实时查询
- 材料管理混乱:作品提交版本混乱,评审材料丢失时有发生
- 数据统计困难:年度参赛数据需要人工汇总,耗时且易出错
这套系统采用前后端分离架构,微信小程序作为移动端入口,后台使用SpringBoot框架,实现了从竞赛发布、报名、评审到证书发放的全流程数字化管理。特别值得一提的是,我们针对微信小程序的特点做了深度优化,比如:
- 利用微信原生API实现扫码快速报名
- 集成微信消息模板实现关键节点通知
- 采用分包加载技术控制小程序体积
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构设计
2.1 整体技术栈选型
经过技术评估,我们最终确定的架构方案是:
code复制前端:微信小程序 + Vue.js
后端:SpringBoot 2.7 + MyBatis-Plus
数据库:MySQL 8.0
中间件:Redis + RabbitMQ
选择这套技术栈主要基于以下考虑:
- 微信小程序覆盖率高,无需额外安装APP
- SpringBoot的自动配置特性大幅减少XML配置
- MyBatis-Plus的ActiveRecord模式简化DAO层开发
- Redis缓存热点数据(如竞赛列表)
- RabbitMQ异步处理文件转码等耗时操作
2.2 数据库设计要点
核心表结构设计时特别注意了以下几点:
sql复制CREATE TABLE `competition` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL COMMENT '竞赛名称',
`start_time` datetime NOT NULL COMMENT '开始时间',
`end_time` datetime NOT NULL COMMENT '结束时间',
`signup_deadline` datetime NOT NULL COMMENT '报名截止',
`max_team_size` int DEFAULT '5' COMMENT '最大组队人数',
`status` tinyint DEFAULT '0' COMMENT '0未开始 1进行中 2已结束',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 特别注意:使用utf8mb4编码支持emoji表情
-- 所有时间字段统一使用datetime类型
-- 状态字段使用tinyint而非varchar
3. 核心功能实现
3.1 微信小程序端关键技术
小程序端采用uni-app框架开发,主要解决了以下技术难点:
页面导航适配方案
javascript复制// 获取导航栏高度
const getNavHeight = () => {
const systemInfo = uni.getSystemInfoSync()
const menu = uni.getMenuButtonBoundingClientRect()
return menu.bottom + menu.top - systemInfo.statusBarHeight
}
文件上传优化
javascript复制uni.uploadFile({
url: 'https://yourdomain.com/api/upload',
filePath: tempFilePaths[0],
name: 'file',
formData: {
'competitionId': this.compId,
'type': 'proposal'
},
success: (res) => {
if (res.statusCode === 413) {
uni.showToast({ title: '文件大小超过10MB限制', icon: 'none' })
}
}
})
3.2 SpringBoot后端关键代码
JWT鉴权实现
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
Excel导出功能
java复制@GetMapping("/export/teams")
public void exportTeams(HttpServletResponse response,
@RequestParam Long competitionId) {
List<TeamVO> teams = teamService.listByCompetition(competitionId);
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition",
"attachment;filename=teams_" + System.currentTimeMillis() + ".xlsx");
EasyExcel.write(response.getOutputStream(), TeamVO.class)
.sheet("参赛队伍")
.doWrite(teams);
}
4. 开发中的典型问题与解决方案
4.1 微信小程序登录态维护
初期直接使用微信的code2session接口获取openid,但存在以下问题:
- 每次启动小程序都需要重新登录
- 无法与后台用户体系关联
最终解决方案:
- 小程序端获取code后传给后台
- 后台用code换取openid和session_key
- 生成自定义token返回给小程序
- 后续请求携带token进行鉴权
java复制public String wxLogin(String code) {
String url = "https://api.weixin.qq.com/sns/jscode2session" +
"?appid=" + appId +
"&secret=" + appSecret +
"&js_code=" + code +
"&grant_type=authorization_code";
WxSessionResponse response = restTemplate.getForObject(url, WxSessionResponse.class);
if(response.getErrcode() != null) {
throw new RuntimeException("微信登录失败: " + response.getErrmsg());
}
return jwtTokenUtil.generateToken(response.getOpenid());
}
4.2 高并发报名场景处理
在热门竞赛开放报名时出现的问题:
- 数据库连接池耗尽
- 重复报名检查失效
优化方案:
- 使用Redis分布式锁控制报名入口
- 采用乐观锁处理并发更新
- 添加数据库唯一索引防止重复报名
java复制public boolean signUp(Long competitionId, Long userId) {
String lockKey = "comp:signup:" + competitionId + ":" + userId;
try {
// 获取分布式锁,超时时间3秒
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 3, TimeUnit.SECONDS);
if(!locked) {
throw new RuntimeException("操作太频繁,请稍后再试");
}
// 检查是否已报名
if(signupMapper.existsByUserAndCompetition(userId, competitionId)) {
return false;
}
// 乐观锁更新剩余名额
int updated = competitionMapper.reduceQuota(competitionId);
if(updated == 0) {
throw new RuntimeException("名额已满");
}
// 创建报名记录
Signup record = new Signup();
record.setCompetitionId(competitionId);
record.setUserId(userId);
record.setCreateTime(new Date());
signupMapper.insert(record);
return true;
} finally {
redisTemplate.delete(lockKey);
}
}
5. 系统部署与性能优化
5.1 生产环境配置建议
经过压力测试后给出的服务器配置:
- 4核8G云服务器(小程序后台API)
- 2核4G云服务器(管理后台)
- Redis单独部署(建议2G以上内存)
- MySQL建议使用云数据库服务
关键JVM参数:
code复制-server -Xms2048m -Xmx2048m -XX:MaxMetaspaceSize=512m
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
5.2 微信小程序优化技巧
-
图片加载优化:
- 使用webp格式替代png/jpg
- 实现懒加载和渐进式加载
- CDN加速静态资源
-
setData性能优化:
javascript复制// 不好的写法
this.setData({
a: 1,
b: 2,
c: 3
})
// 推荐的写法
this.setData({ 'obj.a': 1 })
this.setData({ 'obj.b': 2 })
this.setData({ 'obj.c': 3 })
- 分包加载策略:
code复制├── pages
│ ├── index
│ ├── competition
│ └── user
├── subpackages
│ ├── admin
│ └── judge
6. 项目文档规范
6.1 接口文档示例(Swagger)
java复制@Api(tags = "竞赛管理")
@RestController
@RequestMapping("/api/competition")
public class CompetitionController {
@ApiOperation("获取竞赛详情")
@GetMapping("/{id}")
public Result<CompetitionVO> getDetail(
@ApiParam(value = "竞赛ID", required = true)
@PathVariable Long id) {
// ...
}
}
6.2 数据库变更管理
采用Flyway进行数据库版本控制:
code复制resources/db/migration/
├── V1__Initial_schema.sql
├── V2__Add_judge_columns.sql
└── V3__Create_indexes.sql
变更脚本示例:
sql复制-- V2__Add_judge_columns.sql
ALTER TABLE competition
ADD COLUMN judge_count INT NOT NULL DEFAULT 3,
ADD COLUMN judge_rule VARCHAR(50) COMMENT '评分规则';
7. 扩展功能建议
根据用户反馈,后续可以考虑增加:
-
智能组队功能:
- 基于标签匹配的自动组队
- 组队需求发布墙
-
评审专家系统:
- 双盲评审机制
- 分数自动统计与排名
-
数据分析看板:
- 历年参赛数据对比
- 院系/专业维度分析
-
微信消息模板优化:
javascript复制// 订阅消息模板
wx.requestSubscribeMessage({
tmplIds: ['模板ID1', '模板ID2'],
success(res) {
console.log('订阅结果', res)
}
})
在实现这类系统时,最重要的是保持业务逻辑的清晰划分。我们采用DDD领域驱动设计的思想,将系统划分为:竞赛上下文、用户上下文、评审上下文等,每个上下文有自己独立的领域模型和数据库schema。这种架构在后期的功能扩展中表现出了很好的适应性。
